Nginx 反向代理后,Java 服务端为什么拿不到真实客户端 IP?

一次操作日志全是服务器 IP 的排查记录


问题现象

线上环境通过 Nginx 反向代理访问 Java 后端服务,操作日志里记录的客户端 IP 永远是服务器公网 IP,而不是真实用户的 IP。诡异的是,Nginx 的 access.log 里能正常看到客户端真实 IP。

排查过程

先看服务端获取 IP 的代码,典型写法:

public static String getLocalIp(HttpServletRequest request) {
    String remoteAddr = getIpAddr(request);
    String forwarded = request.getHeader("X-Forwarded-For");
    String realIp = request.getHeader("X-Real-IP");

    String ip = null;
    if (realIp == null) {
        if (forwarded == null) {
            ip = remoteAddr;
        } else {
            ip = remoteAddr + "/" + forwarded.split(",")[0];
        }
    } else {
        if (realIp.equals(forwarded)) {
            ip = realIp;
        } else {
            if (forwarded != null) {
                forwarded = forwarded.split(",")[0];
            }
            ip = realIp + "/" + forwarded;
        }
    }
    return ip;
}

private static String getIpAddr(HttpServletRequest request) {
    String ip = request.getHeader("X-Real-IP");
    if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
        return ip;
    }
    ip = request.getHeader("X-Forwarded-For");
    if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
        int index = ip.indexOf(',');
        if (index != -1) {
            return ip.substring(0, index);
        } else {
            return ip;
        }
    } else {
        return request.getRemoteAddr();
    }
}

代码逻辑没问题,先取 X-Real-IP,再取 X-Forwarded-For,最后 fallback 到 getRemoteAddr()。既然拿到的永远是服务器 IP,说明前面两个 Header 都是 null,走了最后的 fallback。

那问题就很明确了——Nginx 没有把客户端 IP 通过 Header 传给后端

定位 Nginx 配置

看 Nginx 配置中反向代理的部分:

location /jshERP-boot/ {
    proxy_pass http://www.xnysj.cn:9999/jshERP-boot/;
    proxy_set_header Host $host;
    proxy_connect_timeout 10s;
    proxy_read_timeout 300s;
    proxy_send_timeout 60s;
}

问题就在这里——只设置了 Host,没有设置 X-Real-IPX-Forwarded-For。Nginx 本身知道客户端 IP(所以 access.log 正常),但它没把这个信息传给后端,后端只能拿到 getRemoteAddr() 返回的 Nginx 服务器 IP。

修复

补两行 proxy_set_header 就行:

location /jshERP-boot/ {
    proxy_pass http://www.xnysj.cn:9999/jshERP-boot/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;                          # 新增
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;      # 新增
    proxy_connect_timeout 10s;
    proxy_read_timeout 300s;
    proxy_send_timeout 60s;
}

改完执行 nginx -t && nginx -s reload,操作日志就能拿到真实客户端 IP 了。

延伸:这两个 Header 的区别

Header 说明
X-Real-IP $remote_addr 直接连接 Nginx 的客户端 IP,永远是单个 IP
X-Forwarded-For $proxy_add_x_forwarded_for 追加式,经过多层代理时会累积成 客户端IP, 代理1, 代理2

一般场景下用 X-Real-IP 就够了,它最直接。X-Forwarded-For 适合多层代理链的场景。

代码简化建议

既然 Nginx 已经设置了 X-Real-IP,服务端的 getIpAddr 方法返回值就是真实客户端 IP,getLocalIp 里那些复杂的拼接逻辑其实可以简化:

public static String getLocalIp(HttpServletRequest request) {
    return getIpAddr(request);
}

X-Real-IP 就是 $remote_addr,是客户端真实 IP,不需要再和 X-Forwarded-For 拼来拼去。


总结

问题 原因 修复
操作日志全是服务器 IP Nginx 没传 X-Real-IP / X-Forwarded-For 加两行 proxy_set_header

排查这类问题的通用思路:先确认服务端收到的 Header 值(打个日志或 debug),如果都是 null 就往前查 Nginx 配置,如果 Header 有值但不对就查多层代理链路。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐