NGINX中的的请求头x_real_ip和x_forwarded_for
转自:https://blog.csdn.net/xiaoxiao_yingzi/article/details/92835704
参考:https://www.cnblogs.com/dissipate/p/13336414.html
普及下各个机器的名称
发送请求方的机器 名称叫客户端。 请求转发和反向代理的机器叫负载均衡或者LB 最终逻辑处理的机器叫WEB机器。【码农写的逻辑基本上都在WEB机器上】
先说下我们的测试的机器IP分布。
客户端IP | 100.100.100.1 |
负载均衡LB | 100.100.100.2 |
web机器 | 100.100.100.3 |
remote_addr
客户端的IP,如果有代理的话表示最后一个代理服务器的IP。Nginx变量。这个变量是建立TCP连接的IP变量。remote_addr所表示的IP是不可更改的。试想下,如果这个变量可随意更改的话,都无法建立正常的TCP连接。
LB上设置
proxy_set_header REMOTE-ADDR 100.100.100.100;
WEB机器上打印
ngx.log(ngx.ERR,ngx.req.get_headers()['remote_addr']) ngx.log(ngx.ERR, ngx.var.remote_addr) [error] 7566#7566: *5914 [lua] test.lua: proxy_set_header REMOTE-ADDR 100.100.100.100 [error] 7566#7566: *5914 [lua] test.lua 100.100.100.2
不管怎么设置,最终Nginx变量的值都是建立连接的IP。而且需要注意的是header里面的值和变量里面的值是不相同的
X-Forwarded-For
Nginx变量,如果每个代理服务器都设置了
proxy_set_header X-Forwarded-For则$proxy_add_x_̲forwarded_for是…remote_addr用逗号合起来,如果请求头中没有X-Forwarded-For则proxy_add_x_̲forwarded_for为remote。
会记录请求的路由顺序。这个变量只是记录请求的服务器路由顺序。因为这个变量不管在客户端还是代理服务商都是可以伪造的。
在使用nginx做反向代理时,我们为了记录整个的代理过程,我们往往会在配置文件中做如下配置:
location / { 省略... proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://1xx.xxx.xxx.xxx; }
我们做下测试
ngx.log(ngx.ERR, ngx.var.http_x_forwarded_for) 100.100.100.1 ,100.100.100.2
基本是按照发起方的请求顺序记录的,显示客户端IP,然后代理服务器的IP。如果有多层代理的话,就是这样的
客户端IP,proxy1,proxy2,proxyN
而且是当前机器追加记录上一个机器的IP。proxy1追加就客户端IP,proxy2追加记录proxy1.
如果请求的时候伪造X-Forwarded-For即加header头 -H ‘X-Forwarded-For:1.1.1.1,2.2.2.2’。就会是
伪造IP,客户端IP,proxy1,proxy2,proxyN
所以说取真实IP直接获取X-Forwarded-For的第一个IP是不合理的。
如果是服务器上,不传递X-Forwarded-For,即proxy_set_header X-Forwarded-For 没有这个。那下一级的X-Forwarded-For这个变量就是空的。所以X-Forwarded-For 这个值主要是proxy_set_header 传递。
X-Real-IP
顾名思义真实IP。这个变量主要是用来记录真实IP。这个值也主要是以来proxy_set_header传递。可以先看下使用
100.100.100.2 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 100.100.100.3 ngx.log(ngx.ERR, ngx.var.http_x_forwarded_for) ngx.log(ngx.ERR, ngx.var.http_x_real_ip) 100.100.100.1,100.100.100.2 100.100.100.1
可以看到,使用得当,x_real_ip是可以很轻松的拿到真实IP。
如果是多级代理的话,一级一级向后传递真实IP。
第一级代理写法 proxy_set_header X-Real-IP $remote_addr; 后面的代理 proxy_set_header X-Real-IP $x_real_ip;
总结
X-Forwarded-For与X-Real-IP 主要依赖proxy_set_header传递,所以想传什么样的值就传什么样的值。Remote-Addr建立连接的IP,有的地方也说是上一跳的IP,这个不依赖header头传递,不可更改。
所以用上面的组合,第一层代理获取到真实IP,remote_addr。使用x_real_ip层层后传,使用x_real_ip在WEB机器上获取到真实IP。
真实IP的获取顺序是 先检查x_real_ip有无值,有就返回。没有再检查Remote-Addr有无值,有就返回。X-Forwarded-For主要记录请求路由顺序,可伪造。
下面进入实战示例!
1.我们测试一下请求经过三层代理的情况,测试设备分配:
- win10 一台
- 运行在win10上的虚拟机centos6-0,ip:192.168.247.131,一级代理
- 运行在win10上的虚拟机centos6-1, ip:192.168.247.132 ,二级代理
- 运行在win10上的虚拟机centos6-2, ip:192.168.247.133 ,三级代理
- 云服务器,应用服务器
2.测试环境配置:
- win10 在/etc/hosts文件中添加192.168.247.131 http://test.proxy.com
- centos6-0,ip:192.168.247.131,安装nginx,把所有请求转发到192.168.247.132
- centos6-1, ip:192.168.247.132,安装nginx,把所有请求转发到192.168.247.133
- centos6-2, ip:192.168.247.133,安装nginx,把所有请求转发到云服务器
- 在云服务器上的日志中打印http header中的X-Forwarded-For信息
- 防火墙可以关闭掉,防止win10请求无法进入代理链
3.nginx配置文件
#centos6-0,ip:192.168.247.131 ,nginx.conflocation
location / { root html; index index.html index.htm index.php; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.247.132; }
#centos6-1,ip:192.168.247.132 ,nginx.conf
location / { root html; index index.html index.htm index.php; #proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.247.133; }
#centos6-2,ip:192.168.247.133 ,nginx.conf
location / { root html; index index.html index.htm index.php; #proxy_set_header X-Real-IP $x_real_ip; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://123.206.96.111; }
#云服务器方便起见在日志中设置打印$http_x_forwarded_for,进行观察
log_format main '$http_x_forwarded_for|$http_x_real_ip|$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';
4.基于上面的配置在win10浏览器输入:"http://test.proxy.com" 查看云服务器日志打印结果如下:
192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:18:20:27 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"
192.168.247.1, 192.168.247.131, 192.168.247.132 为$http_x_forwarded_for内容,显然记录了代理过程,其中192.168.247.1是客户端ip
192.168.247.1 为基于上述设置的真实IP(不一定准确)
101.254.182.6 公网IP
继续。。。
我们要仔细测试一下在不同代理服务器设置X-FORWARDED-FOR在应用服务器拿到的$http_x_forwarded_for有何不同
1.只在proxy01设置X-FORWARDED-FOR, 在proxy02,proxy03配置文件中注释掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192.168.247.1|192.168.247.1|101.254.182.6 - - [22/May/2017:18:52:49 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1"
2.只在proxy02设置X-FORWARDED-FOR, 在proxy01,proxy03配置文件中注释掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192.168.247.131|192.168.247.1|101.254.182.6 - - [22/May/2017:18:59:59 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.131"
3.只在proxy03设置X-FORWARDED-FOR, 在proxy01,proxy02配置文件中注释掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:19:01:27 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.132"
4.只在proxy01,proxy03设置X-FORWARDED-FOR, 在proxy02配置文件中注释掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192.168.247.1, 192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:19:05:49 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.132"
5.只在proxy02,proxy03设置X-FORWARDED-FOR, 在proxy01配置文件中注释掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192.168.247.131, 192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:19:08:39 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.131, 192.168.247.132"
6.只在proxy01,proxy02设置X-FORWARDED-FOR, 在proxy03配置文件中注释掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192.168.247.1, 192.168.247.131|192.168.247.1|101.254.182.6 - - [22/May/2017:19:10:40 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131"
小结:
- 通过以上几种情况我们可以了解到设置X-Forwarded-For是一个可叠加的过程,后面的代理会把前面代理的IP加入X-Forwarded-For,类似于python的列表append的作用.
- 我们看到在三层代理情况下无论如何设置,应用服务器不可能从$http_x_forwarded_for拿到与它直连的这台服务器的ip(proxy03 ip),此时我们可以使用$remote_addr(远程ip,表示直连的那台代理).一句话,当前服务器无法通过$http_x_forwarded_for获得上级代理或者客户端的ip,应该使用$remote_addr.
- 在代理过程中至少有一个代理设置了proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;否则后面代理或者应用服务器无法获得相关信息.
- 注意,应用服务器可以通过$proxy_add_x_forwarded_for客户端IP(只要至少proxy01代理设置了proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;我们取第一IP就好了),但是我们要考虑客户端伪造头部的情况,如下示例:
假设我们在所有代理都加上了proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;然后我们在proxy01机器上本机curl代替win10模拟一个客户端请求,在proxy01上执行: curl localhost/admin -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-IP: 2.2.2.2'
1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132|127.0.0.1|101.254.182.6 - - [23/May/2017:11:02:09 +0800] "GET /admin HTTP/1.0" 301 263 "-" "curl/7.15.5 (i386-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5" "1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132"
可以看到,1.1.1.1放到了最前面,所以我们不能够想当然的去取第一个ip作为客户端的这是IP.这里127.0.0.1是真实IP.
- 虽然X-Forwarded-For可以伪造,但是对我们依然有用,比如我们就从proxy01代理往后截取就行了,这样就能做到直接忽视伪造得IP.
-
引用本配置的X-Forwarded-For和X-Real-IP
$http_x_forwarded_for
$http_x_real_ip
讨论一下X-Real-IP
下面我们看一下有多级代理存在时如何获取客户端真实IP.
首先要明确在header里面的 X-Real-IP只是一个变量,后面的设置会覆盖前面的设置(跟X-Forwarded-For的追加特性区别明显),所以我们一般只在第一个代理设置proxy_set_header X-Real-IP $remote_addr;就好了,然后再应用端直接引用$http_x_real_ip就行.
1.假如我们只在proxy01设置了 X-Real-IP
192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.1|101.254.182.6 - - [23/May/2017:11:23:00 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"
2.假如我们只在proxy02设置了X-Real-IP
192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.131|101.254.182.6 - - [23/May/2017:11:26:22 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"
3.假如我们只在proxy03设置了X-Real-IP
192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.132|101.254.182.6 - - [23/May/2017:11:27:21 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"
4.所有代理都设置X-Real-IP
192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.132|101.254.182.6 - - [23/May/2017:11:29:09 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"
5.强迫症来了,再试一个只设置proxy01,proxy02的看看
192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.131|101.254.182.6 - - [23/May/2017:11:30:36 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"
假如有人假冒X-Real-IP呢?
6. 在proxy01上执行: curl localhost/admin -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-IP: xx.xx.xx.xx'
1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132|192.168.247.131|101.254.182.6 - - [23/May/2017:11:36:02 +0800] "GET /admin HTTP/1.0" 301 263 "-" "curl/7.15.5 (i386-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5" "1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132"
并没有影响.
Java代码获取真实IP(nginx配置成功前提):
1 /** 2 * 获取HTTP用户真实ip 3 * @param request 4 * @return 5 */ 6 public static String getIpAddr(HttpServletRequest request) { 7 String ipAddress; 8 try { 9 ipAddress = request.getHeader("X-Real-IP"); // 小米集团Nginx 10 log.info("ip: X-Real-IP:{}", ipAddress); 11 if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) { 12 ipAddress = request.getHeader("X-Forwarded-For"); // 巴西aws 13 log.info("ip: X-Forwarded-For:{}", ipAddress); 14 } 15 if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) { 16 ipAddress = request.getHeader("x-forwarded-for");// http2必须使用小写,兼容http2 17 log.info("ip: x-forwarded-for:{}", ipAddress); 18 } 19 if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) { 20 ipAddress = request.getHeader("Proxy-Client-IP"); // 暂时不用 21 log.info("ip: Proxy-Client-IP:{}", ipAddress); 22 } 23 if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) { 24 ipAddress = request.getHeader("WL-Proxy-Client-IP"); // 暂时不用 25 log.info("ip: WL-Proxy-Client-IP:{}", ipAddress); 26 } 27 if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) { 28 ipAddress = request.getRemoteAddr(); 29 if (LOCALHOST.equals(ipAddress)) { 30 InetAddress inet = null; 31 try { 32 inet = InetAddress.getLocalHost(); 33 ipAddress = inet.getHostAddress(); 34 } catch (UnknownHostException e) { 35 log.error("getLocalHost error", e); 36 } 37 } 38 log.info("ip: localhost:{}", ipAddress); 39 } 40 // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 41 // "***.***.***.***".length() 42 if (ipAddress != null && ipAddress.length() > 15) { 43 if (ipAddress.indexOf(SEPARATOR) > 0) { 44 ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); 45 } 46 } 47 } catch (Exception e) { 48 ipAddress = ""; 49 } 50 return ipAddress; 51 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2017-03-21 自定义标签(JspFragment类、invoke方法、开发带属性的标签)
2017-03-21 java中Scanner类nextLine()和next()的区别和使用方法
2017-03-21 java 中的Scanner