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

Java 獲取客戶端真實 IP 地址:穿透代理/負載均衡的完整方案

     編輯:Java 綜合教學

痛點:request.getRemoteAddr() 只拿到代理 IP

在生產環境中,應用通常部署在 Nginx、HAProxy、SLB、CDN、K8s Ingress 等多層代理之後。直接調用 request.getRemoteAddr() 只能獲取到直接上游代理的 IP,而非用戶真實 IP。

標準解決方案:X-Forwarded-For 鏈路追蹤

HTTP 標準定義 X-Forwarded-For (XFF) 頭記錄轉發鏈路:

X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip, ...

第一個非未知 IP 即為客戶端真實 IP。

生產級工具類:IpUtils

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 "待實現";
    }
}

Spring Boot / MVC 整合最佳實踐

1. 過濾器統一註入 Request Attribute

@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);
    }
}

2. Controller 优雅獲取

@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;
    }
}

3. Feign / RestTemplate 傳遞真實 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); // 透傳給下游服務
    }
}

Nginx/網關層配置關鍵 (源頭保證)

Java 代碼只能解析頭,**必須確保網關正確設置** :

Nginx 配置

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 配置

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}

K8s Ingress (Nginx Controller)

annotations:
  nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
  nginx.ingress.kubernetes.io/compute-full-forwarded-for: "true

安全防範:防止 IP 偽裝攻擊

  1. 信任邊界原則:只信任直接上游代理傳遞的頭。配置已知代理 IP 白名單,拒絕來路不明請求的 XFF
  2. 白名單機制示例:
    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
    }
  3. 移除/覆蓋客戶端傳遞的偽造頭:網關層強制重寫 XFF,防止客戶端繞過代理直接調用應用

IPv6 兼容性提示

  • IPv6 格式:2001:db8::1、::ffff:192.168.1.1 (IPv4 映射)
  • 工具類建議引入 InetAddress 解析標準化,而非純字符串匹配
  • 數據庫存儲欄位長度建議 45 字符 (IPv6 全長)

總結

獲取真實 IP 的核心鏈路:客戶端 → 邊緣網關 (寫入 XFF/X-Real-IP) → 應用層 (解析頭、驗證信任鏈) → 業務使用。Java 端工具類只負責「解析與兜底」,**源頭治理 (網關配置) 與信任邊界 (白名單) 才是安全的基石**。

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