在生產環境中,應用通常部署在 Nginx、HAProxy、SLB、CDN、K8s Ingress 等多層代理之後。直接調用 request.getRemoteAddr() 只能獲取到直接上游代理的 IP,而非用戶真實 IP。
HTTP 標準定義 X-Forwarded-For (XFF) 頭記錄轉發鏈路:
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip, ...
第一個非未知 IP 即為客戶端真實 IP。
package com.example.util;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
public class IpUtils {
// 常見代理頭優先級 (按標準與通用程度排序)
private static final List IP_HEADER_CANDIDATES = Arrays.asList(
"X-Forwarded-For", // 標準,最常用
"X-Real-IP", // Nginx 常用
"Proxy-Client-IP", // Apache/IBM WebSphere
"WL-Proxy-Client-IP", // WebLogic
"HTTP_CLIENT_IP", // 部分代理
"HTTP_X_FORWARDED_FOR" // 某些容器轉換格式
);
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
* @param request HttpServletRequest
* @return 真實 IP,獲取失敗返回 request.getRemoteAddr()
*/
public static String getRealIp(HttpServletRequest request) {
if (request == null) return "unknown";
for (String header : IP_HEADER_CANDIDATES) {
String ip = request.getHeader(header);
if (isValidIp(ip)) {
// XFF 可能包含多個 IP,取第一個非未知
return parseFirstValidIp(ip);
}
}
// 兜底:直接連接或無代理頭
String remoteAddr = request.getRemoteAddr();
return isValidIp(remoteAddr) ? remoteAddr : "unknown";
}
/** 解析 XFF 鏈路,返回第一個有效 IP */
private static String parseFirstValidIp(String ipChain) {
if (ipChain == null) return UNKNOWN;
String[] ips = ipChain.split(",");
for (String ip : ips) {
ip = ip.trim();
if (isValidIp(ip)) {
return ip;
}
}
return UNKNOWN;
}
/** 校驗 IP 有效性 (排除 unknown、localhost、空值) */
private static boolean isValidIp(String ip) {
return ip != null
&& !ip.isEmpty()
&& !UNKNOWN.equalsIgnoreCase(ip)
&& !LOCALHOST_IPV4.equals(ip)
&& !LOCALHOST_IPV6.equals(ip)
&& !ip.startsWith("10.") // 可選:排除內網 IP (視業務而定)
&& !ip.startsWith("192.168.") // 可選
&& !ip.startsWith("172.16."); // 可選
}
/** 獲取 IP 地址歸屬地 (需接入第三方庫如 IP2Region、MaxMind) */
public static String getIpLocation(String ip) {
// 接入 ip2region / GeoLite2 等離線庫,或調用外部 API
return "待實現";
}
}
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RealIpFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
String realIp = IpUtils.getRealIp(request);
request.setAttribute("REAL_CLIENT_IP", realIp); // 後續 Controller 直接取
chain.doFilter(request, res);
}
}
@RestController
public class DemoController {
@GetMapping("/ip")
public String getIp(HttpServletRequest request) {
// 方式 1:從 Attribute 獲取 (推薦,已過濾)
String ip = (String) request.getAttribute("REAL_CLIENT_IP");
// 方式 2:直接調用工具類
// String ip = IpUtils.getRealIp(request);
return "您的真實 IP: " + ip;
}
}
// Feign 請求攔截器
@Component
public class FeignIpInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String realIp = IpUtils.getRealIp(request);
template.header("X-Real-IP", realIp); // 透傳給下游服務
}
}
Java 代碼只能解析頭,**必須確保網關正確設置** :
location / {
proxy_pass https://backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加客戶端 IP
proxy_set_header X-Real-IP $remote_addr; # 直接客戶端 IP
proxy_set_header Host $host;
}
spring:
cloud:
gateway:
filter:
- name: AddRequestHeader
args:
name: X-Forwarded-For
value: ${request.headers['X-Forwarded-For']:,}${request.remoteAddress.address}
- name: AddRequestHeader
args:
name: X-Real-IP
value: ${request.remoteAddress.address}
annotations:
nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
nginx.ingress.kubernetes.io/compute-full-forwarded-for: "true
private static final Set TRUSTED_PROXIES = Set.of("10.0.0.1", "10.0.0.2", "192.168.1.100");
public static String getRealIpSecure(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
if (!TRUSTED_PROXIES.contains(remoteAddr)) {
// 非受信代理,忽略 XFF,直接用 remoteAddr
return remoteAddr;
}
return getRealIp(request); // 受信代理,解析 XFF
}
2001:db8::1、::ffff:192.168.1.1 (IPv4 映射)InetAddress 解析標準化,而非純字符串匹配獲取真實 IP 的核心鏈路:客戶端 → 邊緣網關 (寫入 XFF/X-Real-IP) → 應用層 (解析頭、驗證信任鏈) → 業務使用。Java 端工具類只負責「解析與兜底」,**源頭治理 (網關配置) 與信任邊界 (白名單) 才是安全的基石**。