fix: harden gateway auth and credential handling

This commit is contained in:
wjh
2026-10-09 15:47:56 +08:00
parent 00a9dcaf2f
commit 12920d698a
19 changed files with 1168 additions and 250 deletions
+100 -72
View File
@@ -4,11 +4,13 @@ import jakarta.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
/**
* IP 获取与白名单校验工具类
* IP 获取与白名单校验工具类 (具备可信反向代理校验防御)
*/
@Slf4j
public class IpUtils {
@@ -18,13 +20,47 @@ public class IpUtils {
private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
/**
* 获取客户端真实 IP(兼容多级 Nginx / 反向代理 / 负载均衡)
* 默认信任的内网/本地反向代理 IP 网段 (如本地 Nginx / 宿主机代理)
*/
private static final List<String> TRUSTED_PROXIES = new CopyOnWriteArrayList<>(Arrays.asList(
"127.0.0.1",
"::1",
"0:0:0:0:0:0:0:1",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
));
public static void setTrustedProxies(String proxiesConfig) {
if (StringUtils.hasText(proxiesConfig)) {
TRUSTED_PROXIES.clear();
for (String p : proxiesConfig.split("[,;\\s]+")) {
if (StringUtils.hasText(p)) {
TRUSTED_PROXIES.add(p.trim());
}
}
}
}
/**
* 获取客户端真实 IP(仅在直接对端 RemoteAddr 属于可信反向代理时,才信任反代传递的 IP 头)
*/
public static String getClientIp(HttpServletRequest request) {
if (request == null) {
return UNKNOWN;
}
String remoteAddr = request.getRemoteAddr();
if (LOCALHOST_IPV6.equals(remoteAddr)) {
remoteAddr = LOCALHOST_IPV4;
}
// 核心安全防伪造:如果直连来源不是受信任的反向代理,直接使用 remoteAddr,严禁采信伪造的 X-Forwarded-For 等头部
if (!isTrustedProxy(remoteAddr)) {
return remoteAddr != null ? remoteAddr.trim() : UNKNOWN;
}
// 仅在确认对端为受信任反向代理的前提下,提取代理传递的源 IP
String ip = request.getHeader("X-Forwarded-For");
if (isInvalidIp(ip)) {
ip = request.getHeader("X-Real-IP");
@@ -36,13 +72,7 @@ public class IpUtils {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (isInvalidIp(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (isInvalidIp(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (isInvalidIp(ip)) {
ip = request.getRemoteAddr();
ip = remoteAddr;
}
// 多级反向代理会有多个逗号分隔的 IP,首个为客户端真实源 IP
@@ -63,19 +93,27 @@ public class IpUtils {
return ip != null ? ip.trim() : UNKNOWN;
}
private static boolean isTrustedProxy(String ip) {
if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
return false;
}
String normalized = LOCALHOST_IPV6.equals(ip.trim()) ? LOCALHOST_IPV4 : ip.trim();
for (String trustedPattern : TRUSTED_PROXIES) {
if (matchIp(normalized, trustedPattern)) {
return true;
}
}
return false;
}
private static boolean isInvalidIp(String ip) {
return !StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip.trim());
}
/**
* 校验客户端 IP 是否匹配白名单
*
* @param clientIp 客户端请求真实 IP
* @param whitelist 白名单配置(支持英文逗号、分号、中文逗号、换行分隔;留空表示不限制;支持精确 IP、CIDR 网段如 192.168.1.0/24、通配符如 192.168.1.*)
* @return true 允许通过,false 拦截
*/
public static boolean isAllowed(String clientIp, String whitelist) {
// 白名单为空则不限制
if (!StringUtils.hasText(whitelist)) {
return true;
}
@@ -83,10 +121,8 @@ public class IpUtils {
return false;
}
// 统一规范化 localhost
String normalizedClientIp = LOCALHOST_IPV6.equals(clientIp.trim()) ? LOCALHOST_IPV4 : clientIp.trim();
// 支持多种常见分隔符
String[] rules = whitelist.split("[,;,\\n\\r]+");
for (String rule : rules) {
String pattern = rule.trim();
@@ -99,75 +135,67 @@ public class IpUtils {
}
/**
* 单条规则匹配
* IP 规则匹配:支持精确 IP、通配符(192.168.1.*)、CIDR 网段(192.168.1.0/24)
*/
private static boolean matchIp(String clientIp, String pattern) {
if ("*".equals(pattern)) {
return true;
}
// 规范化 localhost
if (("localhost".equalsIgnoreCase(pattern) || LOCALHOST_IPV6.equals(pattern)) && LOCALHOST_IPV4.equals(clientIp)) {
return true;
}
// 1. 精确匹配
if (pattern.equalsIgnoreCase(clientIp)) {
return true;
}
// 2. 通配符匹配 (如 192.168.1.* -> 转换为 CIDR 192.168.1.0/24 逻辑)
if (pattern.contains("*")) {
String regex = pattern.replace(".", "\\.").replace("*", ".*");
return clientIp.matches(regex);
}
// 3. CIDR 网段匹配 (如 192.168.1.0/24)
if (pattern.contains("/")) {
return isInCidrRange(clientIp, pattern);
public static boolean matchIp(String ip, String pattern) {
if (ip == null || pattern == null) {
return false;
}
return false;
pattern = pattern.trim();
ip = ip.trim();
if (LOCALHOST_IPV6.equals(ip)) ip = LOCALHOST_IPV4;
if (LOCALHOST_IPV6.equals(pattern)) pattern = LOCALHOST_IPV4;
if ("*".equals(pattern) || "0.0.0.0/0".equals(pattern)) {
return true;
}
if (pattern.contains("/")) {
return matchCidr(ip, pattern);
}
if (pattern.contains("*")) {
return matchWildcard(ip, pattern);
}
return ip.equalsIgnoreCase(pattern);
}
/**
* 校验 IPv4 是否在 CIDR 网段内
*/
private static boolean isInCidrRange(String ipStr, String cidr) {
private static boolean matchWildcard(String ip, String pattern) {
String regex = pattern.replace(".", "\\.").replace("*", ".*");
return ip.matches(regex);
}
private static boolean matchCidr(String ip, String cidr) {
try {
String[] parts = cidr.split("/");
if (parts.length != 2) {
return false;
}
int maskBits = Integer.parseInt(parts[1].trim());
if (maskBits < 0 || maskBits > 32) {
return false;
if (parts.length != 2) return false;
String network = parts[0];
int prefixLength = Integer.parseInt(parts[1]);
byte[] ipBytes = InetAddress.getByName(ip).getAddress();
byte[] netBytes = InetAddress.getByName(network).getAddress();
if (ipBytes.length != netBytes.length) return false;
int byteCount = prefixLength / 8;
int bitCount = prefixLength % 8;
for (int i = 0; i < byteCount; i++) {
if (ipBytes[i] != netBytes[i]) return false;
}
InetAddress targetAddr = InetAddress.getByName(ipStr);
InetAddress subnetAddr = InetAddress.getByName(parts[0].trim());
byte[] targetBytes = targetAddr.getAddress();
byte[] subnetBytes = subnetAddr.getAddress();
if (targetBytes.length != subnetBytes.length) {
return false;
}
int fullBytes = maskBits / 8;
for (int i = 0; i < fullBytes; i++) {
if (targetBytes[i] != subnetBytes[i]) {
return false;
}
}
int remainingBits = maskBits % 8;
if (remainingBits > 0 && fullBytes < targetBytes.length) {
int mask = (0xFF << (8 - remainingBits)) & 0xFF;
if ((targetBytes[fullBytes] & mask) != (subnetBytes[fullBytes] & mask)) {
return false;
}
if (bitCount > 0 && byteCount < ipBytes.length) {
int mask = (0xFF << (8 - bitCount)) & 0xFF;
return (ipBytes[byteCount] & mask) == (netBytes[byteCount] & mask);
}
return true;
} catch (UnknownHostException | NumberFormatException e) {
log.warn("CIDR 网段解析校验异常: ip={}, cidr={}, error={}", ipStr, cidr, e.getMessage());
log.warn("CIDR 解析错误: ip={}, cidr={}, err={}", ip, cidr, e.getMessage());
return false;
}
}