add漏掉的文件
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package com.yunhai.userbase.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 内部开放接口(/oapi/internal/*)来源 IP 白名单过滤器。<p>
|
||||
* 防止密钥兜底端点(/oapi/internal/sign-key/{appId})被公网直接枚举调用;
|
||||
* 只允许内网 / 显式配置的白名单来源通过,未命中返回 403。<p>
|
||||
* 支持 X-Forwarded-For / X-Real-IP 等常见代理头解析,取最左侧真实来源 IP 做校验。
|
||||
*/
|
||||
public class SignKeyInternalIpFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SignKeyInternalIpFilter.class);
|
||||
|
||||
private final SignKeyInternalProperties properties;
|
||||
|
||||
public SignKeyInternalIpFilter(SignKeyInternalProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
if (!properties.isEnabled()) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String sourceIp = resolveSourceIp(request);
|
||||
List<String> effectiveIps = properties.getEffectiveIps();
|
||||
if (CollectionUtils.isEmpty(effectiveIps) || matchAny(sourceIp, effectiveIps)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
log.warn("[SignKeyInternal] 拒绝非白名单来源访问内部接口: uri={}, sourceIp={}, whitelist={}",
|
||||
request.getRequestURI(), sourceIp, effectiveIps);
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":403,\"msg\":\"内部接口不允许从该来源访问\",\"data\":null}");
|
||||
response.getWriter().flush();
|
||||
}
|
||||
|
||||
private static String resolveSourceIp(HttpServletRequest request) {
|
||||
String[] headers = new String[] {
|
||||
"X-Forwarded-For",
|
||||
"X-Real-IP",
|
||||
"Proxy-Client-IP",
|
||||
"WL-Proxy-Client-IP",
|
||||
"HTTP_CLIENT_IP",
|
||||
"HTTP_X_FORWARDED_FOR"
|
||||
};
|
||||
for (String header : headers) {
|
||||
String raw = request.getHeader(header);
|
||||
if (raw == null || raw.isEmpty() || "unknown".equalsIgnoreCase(raw)) {
|
||||
continue;
|
||||
}
|
||||
// X-Forwarded-For 可能是 多个IP逗号分隔,取最左侧
|
||||
String first = raw.split(",")[0].trim();
|
||||
if (!first.isEmpty()) {
|
||||
return first;
|
||||
}
|
||||
}
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
private static boolean matchAny(String ip, List<String> cidrs) {
|
||||
for (String cidr : cidrs) {
|
||||
if (matchCidr(ip, cidr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean matchCidr(String ip, String cidr) {
|
||||
if (ip == null || cidr == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
int prefixLen;
|
||||
String network;
|
||||
if (cidr.contains("/")) {
|
||||
String[] parts = cidr.split("/");
|
||||
network = parts[0].trim();
|
||||
prefixLen = Integer.parseInt(parts[1].trim());
|
||||
} else {
|
||||
network = cidr.trim();
|
||||
// 判断地址族决定默认前缀长度
|
||||
InetAddress na = InetAddress.getByName(network);
|
||||
prefixLen = (na instanceof Inet6Address) ? 128 : 32;
|
||||
}
|
||||
InetAddress ipAddr = InetAddress.getByName(ip);
|
||||
InetAddress netAddr = InetAddress.getByName(network);
|
||||
if (ipAddr.getClass() != netAddr.getClass()) {
|
||||
return false;
|
||||
}
|
||||
byte[] ipBytes = ipAddr.getAddress();
|
||||
byte[] netBytes = netAddr.getAddress();
|
||||
int bits = prefixLen;
|
||||
for (int i = 0; i < ipBytes.length && bits > 0; i++) {
|
||||
int mask = bits >= 8 ? 0xFF : (0xFF << (8 - bits));
|
||||
if ((ipBytes[i] & mask) != (netBytes[i] & mask)) {
|
||||
return false;
|
||||
}
|
||||
bits -= 8;
|
||||
}
|
||||
return true;
|
||||
} catch (UnknownHostException | NumberFormatException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.yunhai.userbase.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 内部开放接口(/oapi/internal/*)访问白名单 IP 配置。<br>
|
||||
* 支持单 IP、CIDR(如 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.1/32)。<br>
|
||||
* <p>
|
||||
* 可在 Nacos 上配置,例如:<pre>
|
||||
* yh:
|
||||
* sign-key-internal:
|
||||
* ips:
|
||||
* - 10.0.0.0/8
|
||||
* - 172.16.0.0/12
|
||||
* - 192.168.0.0/16
|
||||
* - 127.0.0.1/32
|
||||
* - 172.20.10.5/32 # 某台固定机器
|
||||
* </pre>
|
||||
* 未配置时,默认允许 10/172.16-31/192.168 私有段 + 127.0.0.1(方便本地开发/内网部署)。
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "yh.sign-key-internal")
|
||||
public class SignKeyInternalProperties {
|
||||
|
||||
/** 显式允许的来源 IP(CIDR 或 单 IP)。为空时使用默认私有段规则。 */
|
||||
private List<String> ips = new ArrayList<>();
|
||||
|
||||
/** 是否启用白名单校验;设置 false 可临时关闭(生产不建议)。 */
|
||||
private boolean enabled = true;
|
||||
|
||||
public List<String> getIps() { return ips; }
|
||||
|
||||
public void setIps(List<String> ips) { this.ips = ips; }
|
||||
|
||||
public boolean isEnabled() { return enabled; }
|
||||
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
|
||||
/** 未配置 ips 时返回默认私有段 + localhost 规则。 */
|
||||
public List<String> getEffectiveIps() {
|
||||
if (ips != null && !ips.isEmpty()) {
|
||||
return ips;
|
||||
}
|
||||
return Arrays.asList(
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"127.0.0.1/32",
|
||||
"0:0:0:0:0:0:0:1/128"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yunhai.userbase.model.sysuser;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/8/12
|
||||
* @description 批量设置用户状态请求
|
||||
*/
|
||||
@Data
|
||||
public class BatchChangeStatusRequest {
|
||||
|
||||
/** 待设置用户ID列表 */
|
||||
@NotEmpty(message = "待设置用户不能为空")
|
||||
private List<Integer> userIds;
|
||||
|
||||
/** 是否禁用:true=禁用,false=启用 */
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Boolean disabled;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yunhai.userbase.model.sysuser;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/8/12
|
||||
* @description 批量从当前组删除用户请求
|
||||
*/
|
||||
@Data
|
||||
public class BatchDeleteRequest {
|
||||
|
||||
/** 待删除用户ID列表 */
|
||||
@NotEmpty(message = "待删除用户不能为空")
|
||||
private List<Integer> userIds;
|
||||
|
||||
/** 当前组ID */
|
||||
@NotNull(message = "组ID不能为空")
|
||||
private Integer groupId;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.yunhai.userbase.model.sysuser;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/8/12
|
||||
* @description 调整用户在某组内的排序号
|
||||
*/
|
||||
@Data
|
||||
public class ChangeSortNoRequest {
|
||||
|
||||
/** 所属分组(排序号按组维度存储,必须明确是哪个组) */
|
||||
@NotNull(message = "所属分组不能为空")
|
||||
private Integer groupId;
|
||||
|
||||
/**
|
||||
* 组内排序号:1~999 的正整数;越小越靠前。<br>
|
||||
* 允许为 null —— 表示清除排序号,列表回退到 user_id 升序兜底。<br>
|
||||
* 注意:@Min/@Max 对 null 不生效,null 由业务层解释为「清除」。
|
||||
*/
|
||||
@Min(1)
|
||||
@Max(999)
|
||||
private Integer sortNo;
|
||||
}
|
||||
Reference in New Issue
Block a user