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
一般只记录真实发出请求的客户端IP
相关请求头
后端能获取到的三个IP地址
request.getHeader("X-Forwarded-For");
request.getHeader("X-Real-IP");
request.getRemoteAddr();
返回的数据格式
{"rs":1,"code":0,"address":"中国 河南省 郑州市 电信","ip":"123.53.36.16","isDomain":0}
假如我们的请求经过如下反向代理
请求 => proxy1 => proxy2 => proxy3 => 后端服务
假如
6.6.6.6
1.1.1.1
2.2.2.2
3.3.3.3
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 的配置中都加上:
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了
可以使用nginx的 ngx_http_realip_module 模块,从 X-Forwarded-For 或其他属性中提取真实IP。此处以 X-Forwarded-For 结合该模块为例子,需要做两件事:
一是请求途径的各代理需要设置
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
二是利用 realip 模块获取真实IP
这里proxy3的部分配置(proxy3将请求直接转发到后端服务),如下:
server {
location / {
set_real_ip_from 1.1.1.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
}
}
基于上一步的测试数据,试验结果:
$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 传递到后端服务中去。
后端获取
request.getRemoteAddr();
由于客户端可以自行传递X-Forwarded-For,因此,可以在第一个代理处重置其值,达到忽略客户端传递的X-Forwarded-For的效果。
在 proxy1 中进行如下配置:
proxy_set_header X-Forwarded-For $remote_addr;
后端代码
String ip = request.getHeader("X-Forwarded-For");
if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if(index!=-1){
return ip.substring(0,index);
}else{
return ip;
}
}
由于proxy1的 remote_addr 是客户端真实IP,因此在 proxy1 中将X-Real-IP的值设置为 remote_addr 即可。
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日志中可以看到该值
后端获取
request.getHeader("X-Real-IP");
仅后端判断,如果人为添加:X-Forwarded-For=192.168.1.1,192.168.1.2
这样的头,是没法获取真实的IP的,但是实际使用中我们可忽略,不确定代理的层级可使用如下代码获取
Nginx代理中配置
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Java获取
/**
* 获取客户端ip地址
* @param request
* @return
*/
public static String getRealIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if( ip.indexOf(",")!=-1 ){
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
Nginx可用变量
http://nginx.org/en/docs/http/ngx_http_core_module.html#variables
搜索 Embedded Variables