概念
X-Forwarded-For标准格式如下:
X-Forwarded-For: client1, proxy1, proxy2
从标准格式可以看出,X-Forwarded-For头信息可以有多个,中间用逗号分隔,第一项为真实的客户端ip,剩下的就是曾经经过的代理或负载均衡的ip地址,经过几个就会出现几个。
X-Forwarded-For和X-Real-IP
X-Forwarded-For
是用于记录代理信息的,每经过一级代理(匿名代理除外),代理服务器都会把这次请求的来源IP追加在X-Forwarded-For
中X-Real-IP
一般只记录真实发出请求的客户端IPX-Forwarded-For在正向(如squid)反向(如nginx)代理中都是标准用法,
而正向代理中是没有X-Real-IP相关的标准的,也就是说,如果用户访问你的 nginx反向代理之前,还经过了一层正向代理,你即使在nginx中配置了X-Real-IP,取到的也只是正向代理的IP而不是客户端真实IP
大部分nginx反向代理配置文章中都没有推荐加上X-Real-IP ,而只有X-Forwarded-For,因此更通用的做法自然是取X-Forwarded-For
多级代理很少见,只有一级代理的情况下二者是等效的
如果有多级代理,X-Forwarded-For效果是大于X-Real-IP的,可以记录完整的代理链路
相关请求头
X-Forwarded-For :这是一个 Squid 开发的字段,只有在通过了HTTP代理或者负载均衡服务器时才会添加该项。
格式为X-Forwarded-For:client1,proxy1,proxy2,一般情况下,第一个ip为客户端真实ip,后面的为经过的代理服务器ip。现在大部分的代理都会加上这个请求头。
Proxy-Client-IP/WL- Proxy-Client-IP :这个一般是经过apache http服务器的请求才会有,用apache http做代理时一般会加上Proxy-Client-IP请求头,而WL-Proxy-Client-IP是他的weblogic插件加上的头。
- HTTP_CLIENT_IP :有些代理服务器会加上此请求头。
- X-Real-IP :Nginx代理一般会加上此请求头。
后端能获取到的三个IP地址
1 | request.getHeader("X-Forwarded-For"); |
获取IP的接口
返回的数据格式
1 | {"rs":1,"code":0,"address":"中国 河南省 郑州市 电信","ip":"123.53.36.16","isDomain":0} |
JSONP调用
返回的数据
1 | if(window.IPCallBack) {IPCallBack({"ip":"123.53.36.16","pro":"河南省","proCode":"410000","city":"郑州市","cityCode":"410100","region":"","regionCode":"0","addr":"河南省郑州市 电信","regionNames":"","err":""});} |
Nginx代理获取到的IP
假如我们的请求经过如下反向代理
请求 => proxy1 => proxy2 => proxy3 => 后端服务
假如
- 请求的IP为
6.6.6.6
- proxy1的IP为
1.1.1.1
- proxy2的IP为
2.2.2.2
- proxy3的IP为
3.3.3.3
- 后端服务的IP为
8.8.8.8
$remote_addr
获取到的地址
proxy1:6.6.6.6
proxy2:1.1.1.1
proxy3:2.2.2.2
结果
只有第一个代理能获取到真实的IP
$proxy_add_x_forwarded_for
获取到的地址
proxy1:6.6.6.6
proxy2:6.6.6.6,1.1.1.1
proxy3:6.6.6.6,1.1.1.1,2.2.2.2
proxy1、2、3 的配置中都加上:
1 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
结果
获取到的IP中第一个都是真实的IP
但客户端请求头中人为添加:X-Forwarded-For=192.168.1.1,192.168.1.2
,再看看结果:
proxy1:192.168.1.1,192.168.1.2,6.6.6.6
proxy2:192.168.1.1,192.168.1.2,6.6.6.6,1.1.1.1
proxy3:192.168.1.1,192.168.1.2,6.6.6.6,1.1.1.1,2.2.2.2
结果
第一个IP都不是真实的IP了
怎么获取真实的IP
使用 X-Forwarded-For + realip模块
可以使用nginx的 ngx_http_realip_module 模块,从 X-Forwarded-For 或其他属性中提取真实IP。此处以 X-Forwarded-For 结合该模块为例子,需要做两件事:
一是请求途径的各代理需要设置
1
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
二是利用 realip 模块获取真实IP
这里proxy3的部分配置(proxy3将请求直接转发到后端服务),如下:
1 | server { |
- set_real_ip_from:表示从何处获取真实IP(解决安全问题,只认可自己信赖的IP),可以是IP或子网等, 可以设置多个set_real_ip_from。
- real_ip_header:表示从哪个header属性中获取真实IP
- real_ip_recursive:递归检索真实IP,若从 X-Forwarded-For 中获取,则需递归检索;若像从X-Real-IP中获取,则无需递归。
基于上一步的测试数据,试验结果:
$remote_addr
获取到的地址
proxy1:6.6.6.6
proxy2:1.1.1.1
proxy3:6.6.6.6
$proxy_add_x_forwarded_for
获取到的地址
proxy1:192.168.1.1,192.168.1.2,6.6.6.6
proxy2:192.168.1.1,192.168.1.2,6.6.6.6,1.1.1.1
proxy3:192.168.1.1,192.168.1.2,6.6.6.6,1.1.1.1,2.2.2.2
此时,proxy3 的 $remote_addr
已经拿到了客户端的真实IP 36.157.229.110,然后 proxy3 将 remote_addr 传递到后端服务中去。
后端获取
1 | request.getRemoteAddr(); |
使用 X-Forwarded-For + 重置设置
由于客户端可以自行传递X-Forwarded-For,因此,可以在第一个代理处重置其值,达到忽略客户端传递的X-Forwarded-For的效果。
在 proxy1 中进行如下配置:
1 | proxy_set_header X-Forwarded-For $remote_addr; |
后端代码
1 | String ip = request.getHeader("X-Forwarded-For"); |
使用 X-Real-IP
由于proxy1的 $remote_addr
是客户端真实IP,因此在 proxy1 中将X-Real-IP
的值设置为 $remote_addr
即可。
1 | proxy_set_header X-Real-IP $remote_addr; |
结果为:
$http_x_real_ip
获取到的地址
proxy1:-
proxy2:6.6.6.6
proxy3:6.6.6.6
proxy1 中设置了X-Real-IP的值,proxy2、proxy3日志中可以看到该值
后端获取
1 | request.getHeader("X-Real-IP"); |
仅后端判断
仅后端判断,如果人为添加:X-Forwarded-For=192.168.1.1,192.168.1.2
这样的头,是没法获取真实的IP的,但是实际使用中我们可忽略,不确定代理的层级可使用如下代码获取
Nginx代理中配置
1 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
Java获取
1 | /** |
附录
Nginx可用变量
http://nginx.org/en/docs/http/ngx_http_core_module.html#variables
搜索 Embedded Variables