nginx-通过X-Forwarded-For获取客户端真实IP

前言

在真实生产环境中,最常用的使用nginx作为反向代理来暴露服务。不过在暴露到公网前,我们往往会在前面通过CDN或者云厂商的负载均衡器来暴露我们的服务。

在不通的协议(tcp/http)或场景下,通过X-Forwarded-For获取客户端真实ip的方式往往不同。

配置分析

默认情况下,nginx不会对X-Forwarded-For进行修改。也就是说如果没有额外配置的情况下,nginx前面的负载均衡器或者其他nginx传过来的X-Forwarded-For的值是什么样,nginx反代后还会以一样的值反代下去。

有时候nginx前面的负载均衡器没有传X-Forwarded-For,则需要通过proxy_set_header X-Forwarded-For来修改值。

$proxy_add_x_forwarded_for

$proxy_add_x_forwarded_for会保存X-Forwarded-For中已有的值,并且追加$remote_addr的值,使用逗号隔开。

如果之前X-Forwarded-For中没有值,则修改后X-Forwarded-For中只有$remote_addr的值。

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 

$remote_addr

$remote_addr就是到达nginx前的上一层服务器的真实ip。如果nginx前面使用的是云厂商的负载均衡器,一般$remote_addr有可能就是云厂商负载均衡器实例的ip。为什么这里说有可能?负载均衡器根据监听的协议不同也会分两种情况:

  • 如果是tcp协议,针对四层监听,并且默认开启了“获取客户端真实IP”。那么$remote_addr就是来访者的真实IP地址。
  • 如果是http协议,针对七层监听,并且默认开启了“获取客户端真实IP”。那么负载均衡器一般会通过X-Forwarded-For头将客户端真实IP传递到后端服务器。那么这时候$remote_addr通常就是负载均衡器的实例IP。
proxy_set_header X-Forwarded-For $remote_addr; 

这种配置方式和$proxy_add_x_forwarded_for不一样,不会将$remote_addr的值追加在原来的值后面,而是直接覆盖原有的X-Forwarded-For的值。

$http_x_forwarded_for

nginx中还可以使用$http_x_forwarded_for获取nginx收到的X-Forwarded-For的值。不过一般用于在打印access日志时使用。

如果是上述所说的http协议,针对七层监听,且负载均衡器已经将客户端真实IP放在了X-Forwarded-For中传过来了。那么就不要去修改X-Forwarded-For头,nginx会将传来的值原封不动地再反代到后端服务器去。或者修改就使用$http_x_forwarded_for变量。

当你在 Spring Boot 中尝试通过 `X-Forwarded-For` 获取客户端 IP 时,发现该 Header 为空(即 `request.getHeader("X-Forwarded-For")` 返回 `null`),这说明 **代理层没有正确设置或传递这个头字段**。 我们来详细分析原因并提供解决方案。 --- ## ✅ 问题:`X-Forwarded-For` 为空的原因 | 原因 | 说明 | |------|------| | ❌ Nginx 未配置 `proxy_set_header` | 这是最常见的原因,Nginx 默认不会自动添加 `X-Forwarded-For` | | ❌ 请求未经过代理,直接访问后端 | 比如你绕过前端 Nginx 直接调用后端服务 | | ❌ 多层代理中某一层覆盖/清除了头部 | 某个中间代理(如内部网关)移除了敏感头 | | ❌ 使用了 HTTPS 终止于前端,但后端代理未正确转发 | | ❌ Docker 网络隔离导致请求“看起来”是内部调用 | --- ## ✅ 解决方案:确保每一层代理都正确设置了 `X-Forwarded-For` ### 步骤一:检查并修复 Nginx 配置 #### ✅ 前端 Nginx(处理 Vue + 转发 API 到后端) ```nginx location /api/ { proxy_pass http://backend-springboot:8080/; 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_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } ``` > 🔍 关键点: > - `$proxy_add_x_forwarded_for` 会自动判断是否存在 `X-Forwarded-For`,如果存在就追加当前 `$remote_addr`,否则创建。 > - 如果你不写这一行,Header 就根本不会被设置! --- #### ✅ 后端 Nginx(如果有第二层代理) 如果你的架构是: ``` 用户 → 前端 Nginx → 后端 Nginx → Spring Boot ``` 那么 **后端 Nginx 也必须保留这些头字段**: ```nginx location / { proxy_pass http://localhost:8080; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } ``` 否则前一级设置的 `X-Forwarded-For` 会被丢弃。 --- ### 步骤二:验证 Header 是否真的传到了后端 在 Spring Boot 控制器中打印所有 Headers 来调试: ```java @GetMapping("/debug") public Map<String, String> debugHeaders(HttpServletRequest request) { Map<String, String> headers = new HashMap<>(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); headers.put(name, request.getHeader(name)); } return headers; } ``` 访问 `/debug` 接口,查看返回结果中是否有: ```json "X-Forwarded-For": "1.2.3.4" ``` 如果没有,则证明 **代理层确实没设置**。 --- ### 步骤三:确认请求路径是否经过代理 常见错误场景: - 开发者直接从 Postman 或浏览器访问 `http://localhost:8080/login`(绕过了 Nginx- 在容器内测试 `curl springboot:8080/api`,此时 `$remote_addr` 是另一个容器 IP,且可能不带任何 `X-Forwarded-*` 头 ✅ 正确做法:始终通过入口 Nginx 访问(如 `http://your-domain.com/api/login`) --- ### 步骤四:启用 Spring Boot 的 Forwarded 支持(重要!) 即使 Nginx 设置了 `X-Forwarded-For`,Spring Boot 默认 **不信任这些头**,需要显式开启。 #### 方法 1:在 `application.yml` 中开启 ```yaml server: forwarded-header-enabled: true ``` #### 方法 2:使用 Java 配置类(更灵活) ```java @Configuration public class WebConfig { @Bean public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() { FilterRegistrationBean<ForwardedHeaderFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new ForwardedHeaderFilter()); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); bean.addUrlPatterns("/*"); return bean; } } ``` ✅ 开启后,Spring 会自动将 `X-Forwarded-For` 映射为 `request.getRemoteAddr()` 的值。 > ⚠️ 注意:只在受信网络中开启此功能,防止伪造! --- ### 步骤五:补充备用方案 —— 使用 `X-Real-IP` 有时 `X-Forwarded-For` 仍为空,可以配合使用 `X-Real-IP`: #### Nginx 添加: ```nginx proxy_set_header X-Real-IP $remote_addr; ``` #### Java 工具类改进: ```java public static String getClientIp(HttpServletRequest request) { String xff = request.getHeader("X-Forwarded-For"); if (xff != null && !xff.isEmpty() && !"unknown".equalsIgnoreCase(xff)) { return xff.split(",")[0].trim(); // 第一个IP } String xri = request.getHeader("X-Real-IP"); if (xri != null && !xri.isEmpty() && !"unknown".equalsIgnoreCase(xri)) { return xri.trim(); } return request.getRemoteAddr(); } ``` --- ## ✅ 如何验证问题已解决? 1. 使用浏览器访问你的 Vue 页面,并发起登录请求。 2. 在后端日志中输出 `X-Forwarded-For` 和最终获取IP。 3. 使用公网设备访问(不要用 localhost)。 4. 查看是否能拿到类似 `203.0.113.45` 这样的公网 IP,而不是 `172.x.x.x` 或 `192.168.x.x`。 --- ## ✅ 常见误区提醒 | 错误做法 | 正确做法 | |--------|---------| | 只在最后一层 Nginx 设置 `X-Forwarded-For` | 每一层代理都要设置 | | 直接用 `getRemoteAddr()` | 先读 Header 再 fallback | | 忘记开启 `forwarded-header-enabled` | 开启后才能让 Spring 正确识别 | | 在本地 `docker exec` 测试时不带 Header | 应模拟真实请求链路 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值