feat: 实现IP白名单严格拦截、三方Webhook多租户事件订阅与MCP专线业务调用

This commit is contained in:
wjh
2026-10-09 11:51:23 +08:00
parent c6bd736e7f
commit 00a9dcaf2f
11 changed files with 603 additions and 58 deletions
@@ -0,0 +1,174 @@
package com.iot.gateway.common;
import jakarta.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
/**
* IP 获取与白名单校验工具类
*/
@Slf4j
public class IpUtils {
private static final String UNKNOWN = "unknown";
private static final String LOCALHOST_IPV4 = "127.0.0.1";
private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
/**
* 获取客户端真实 IP(兼容多级 Nginx / 反向代理 / 负载均衡)
*/
public static String getClientIp(HttpServletRequest request) {
if (request == null) {
return UNKNOWN;
}
String ip = request.getHeader("X-Forwarded-For");
if (isInvalidIp(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (isInvalidIp(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (isInvalidIp(ip)) {
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,首个为客户端真实源 IP
if (StringUtils.hasText(ip) && ip.contains(",")) {
for (String subIp : ip.split(",")) {
String trimmed = subIp.trim();
if (!isInvalidIp(trimmed)) {
ip = trimmed;
break;
}
}
}
if (LOCALHOST_IPV6.equals(ip)) {
ip = LOCALHOST_IPV4;
}
return ip != null ? ip.trim() : UNKNOWN;
}
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;
}
if (!StringUtils.hasText(clientIp) || UNKNOWN.equalsIgnoreCase(clientIp)) {
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();
if (!pattern.isEmpty() && matchIp(normalizedClientIp, pattern)) {
return true;
}
}
return false;
}
/**
* 单条规则匹配
*/
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);
}
return false;
}
/**
* 校验 IPv4 是否在 CIDR 网段内
*/
private static boolean isInCidrRange(String ipStr, 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;
}
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;
}
}
return true;
} catch (UnknownHostException | NumberFormatException e) {
log.warn("CIDR 网段解析校验异常: ip={}, cidr={}, error={}", ipStr, cidr, e.getMessage());
return false;
}
}
}