首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 精準獲取客戶端真實 IP 地址方案

     編輯:Java 綜合教學

穿透代理與負載均衡:Java 獲取真實客戶端 IP 最佳實踐

在分佈式架構與雲原生環境下,客戶端請求往往經過 Nginx、網關、CDN、負載均衡器(SLB/ALB)多層代理才到達應用服務器。此時 HttpServletRequest.getRemoteAddr() 只能拿到直接上游的 IP(如 Nginx IP),而非真實用戶 IP。本文提供生產級的 IP 獲取工具方法,涵蓋標準頭部解析、可信代理鏈驗證、IPv6 兼容與安全防範。

一、 核心原理:標準代理頭部

頭部名稱來源/標準說明
X-Forwarded-For (XFF)事實標準 (RFC 7239)格式:client, proxy1, proxy2,最左側為最原始客戶端 IP。可能被偽造。
X-Real-IPNginx/HAProxy 慣例單一 IP 值,通常由邊緣代理設置,相對可信。
ForwardedRFC 7239 標準結構化格式:for=192.0.2.60;proto=https;by=203.0.113.43,支持 IPv6 與多代理鏈。
X-Forwarded-Proto慣例原始協議(http/https),用於判斷是否需重定向。

二、 生產級工具類實現

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;

public final class IpUtil {

    private static final List<String> IP_HEADERS = Arrays.asList(
        "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP",
        "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR", "Forwarded"
    );

    /** 可信代理 IP 白名單(生產環境必須配置,防止頭部偽造) */
    private static final List<String> TRUSTED_PROXIES = Arrays.asList(
        "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" // 內網 CIDR 示例
    );

    private IpUtil() {}

    /**
     * 獲取客戶端真實 IP
     * @param request HttpServletRequest
     * @return 真實 IP,獲取失敗返回 "unknown"
     */
    public static String getRealIp(HttpServletRequest request) {
        if (request == null) return "unknown";

        // 1. 優先解析 RFC 7239 Forwarded 標準頭
        String forwarded = request.getHeader("Forwarded");
        if (forwarded != null) {
            String ip = parseForwardedHeader(forwarded);
            if (isValidIp(ip)) return ip;
        }

        // 2. 遍歷常見代理頭
        for (String header : IP_HEADERS) {
            String value = request.getHeader(header);
            if (value != null && !value.isEmpty() && !"unknown".equalsIgnoreCase(value)) {
                // X-Forwarded-For 可能包含多個 IP,取最左側非私有/非代理 IP
                String[] ips = value.split("\\s*,\\s*");
                for (String ip : ips) {
                    if (isValidIp(ip) && !isPrivateOrProxyIp(ip)) {
                        return ip;
                    }
                }
                // 若全是內網/代理 IP,返回第一個有效 IP
                for (String ip : ips) {
                    if (isValidIp(ip)) return ip;
                }
            }
        }

        // 3. 回退到 RemoteAddr
        String remoteAddr = request.getRemoteAddr();
        return isValidIp(remoteAddr) ? remoteAddr : "unknown";
    }

    /** 解析 Forwarded 頭:for=IP[, for=IP...] */
    private static String parseForwardedHeader(String header) {
        // 簡易解析,生產建議用成熟庫(如 Spring ForwardedHeaderFilter)
        String[] parts = header.split(";\\s*");
        for (String part : parts) {
            if (part.startsWith("for=")) {
                String val = part.substring(4).trim();
                // 去除引號、端口、IPv6 方括號
                val = val.replaceAll("^\"|\"$", "").split(":")[0].replaceAll("^\\[|\\]$", "");
                return val;
            }
        }
        return null;
    }

    /** 基礎 IP 格式校驗(IPv4/IPv6) */
    private static boolean isValidIp(String ip) {
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) return false;
        // 簡易正則,嚴格場景建議用 InetAddress.getByName() 嘗試解析
        return ip.matches("^(\\d{1,3}\\.){3}\\d{1,3}$") || ip.matches("^[0-9a-fA-F:]+$");
    }

    /** 判斷是否為私有 IP 或 受信代理 IP(需結合 CIDR 庫精確匹配) */
    private static boolean isPrivateOrProxyIp(String ip) {
        // 此處為示例邏輯,實際項目請引入 ipmatcher 或 Guava InetAddresses 進行 CIDR 匹配
        return ip.startsWith("10.") || ip.startsWith("192.168.") || 
               ip.matches("^172\\.(1[6-9]|2\\d|3[0-1])\\..*") || 
               ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1");
    }
}

三、 Spring Boot / Spring Cloud 整合最佳實踐

1. 使用 ForwardedHeaderFilter 自動解析

@Bean
public ForwardedHeaderFilter forwardedHeaderFilter() {
    return new ForwardedHeaderFilter(); // 自動將 Forwarded/X-Forwarded-* 映射到 request.getRemoteAddr() 等
}

配合 server.forward-headers-strategy=native(Spring Boot 2.2+)或 server.use-forward-headers=true(舊版),容器層面即可完成 IP 重寫。

2. Gateway/Zuul 統一網關層透傳

  • 在網關層(Spring Cloud Gateway / Zuul / Nginx)統一提取真實 IP,設置自定義頭(如 X-Original-Client-IP)傳遞給下游微服務。
  • 下游服務僅信任來自網關 IP 的請求頭,拒絕客戶端直接攜帶的頭部,杜絕偽造。

3. Nginx 邊緣代理標準配置

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Forwarded "for=$remote_addr;proto=$scheme;by=$server_addr";

四、 安全防範檢查清單

  1. ✅ 配置可信代理白名單:只信任已知負載均衡器/網關 IP 發送的代理頭。
  2. ✅ 拒絕客戶端直接提交 XFF:在 WAF/網關層剝離客戶端發來的 X-Forwarded-For 等頭部。
  3. ✅ 日誌記錄完整鏈路:記錄 RemoteAddr、X-Forwarded-For、X-Real-IP、Forwarded 所有頭部,便於審計追溯。
  4. ✅ IPv6 兼容測試:驗證 [] 方括號格式、Zone ID(如 fe80::1%eth0)解析正確。
  5. ✅ 單元測試覆蓋:構造多級代理、偽造頭、IPv6、內網穿透等場景測試用例。

五、 常見誤區與避坑指南

誤區後果正確做法
直接取 XFF 第一個 IP惡意用戶偽造頭部繞過限流/封禁結合可信代理白名單,從右向左找第一個非代理 IP
忽略 Forwarded 標準頭無法支持 RFC 7239 合規代理鏈優先解析 Forwarded,兼容舊有 XFF
將 127.0.0.1 視為真實 IP本地測試/內部調用污染統計過濾回環/私有地址段
未處理 IPv6 地址雙棧環境下獲取失敗使用 InetAddress 統一解析,存儲標準格式

掌握真實 IP 獲取的核心邏輯與安全邊界,是構建可靠訪問控制、風控分析、審計日誌系統的基石。建議在項目公共模組封裝 IpUtil,並納入代碼審查清單,確保全鏈路一致性與安全性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved