前言:
在初步构建的nginx+tomcat服务集群时, 发现webserver获取到的客户端ip都是同一个, 皆为作为反向代理服务的nginx所在的机器IP. 这不太符合我们的基本需求, 为将来的数据挖掘和分析带来了麻烦.
不过不用担心, 本文将简单介绍其背后的原因和原理, 以及具体的解决方案, ^_^.
原因分析:
webserver在获取客户端ip时, 默认的方式是通过request.getRemoteAddr(), 这种方式本质是从直连的socket中获取到的.
因此客户端直连web server服务器, 则获取到的ip即为真实的client ip地址信息. 不过若通过代理, 则直连的ip会被代理服务器的ip所替代.
通过图中的对比, 我们可以清楚的观察到, 在反向代理模式下, 客户端的socket已经被nginx的socket所代替. 若还是按默认的方式来获取客户端ip, 将失去意义.
解决思路:
nginx是7层代理, 不是lvs的4层代理, 因此不可能在tcp/ip这层做手脚. 只能在http/https这个应用层协议中做文章.
事实上, 其解决方案是基于约定的方案, 需要各方的配合来完成并实现.
nginx的策略是: 往http/https请求中, 添加额外的header信息, 以此来完成真实客户端ip的信息传递.
nginx配置:
先来介绍下nginx中的一些内部变量定义.
$host $remote_addr #来自对端socket的ip地址 $remote_port #来自对端socket的port信息 $proxy_add_x_forwarded_for #http/https请求流经的所有代理的ip
更详细的nginx内部变量, 请参阅"nginx rewrite 参数和例子".
在nginx配置中, 需要在location标签下添加如下项:
proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
对于X-Real-IP, 大家肯定容易接受和理解. 但对于X-Forwarded-For, 大家肯定有些疑惑, 这到底是什么鬼, 具体有什么用呢?
• X-Forwarded-For
简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP,只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项。它不是RFC中定义的标准请求头信息,在squid缓存代理服务器开发文档中可以找到该项的详细介绍。
标准格式如下:X-Forwarded-For: client1, proxy1, proxy2, ...
从标准格式可以看出,X-Forwarded-For头信息可以有多个,中间用逗号分隔,第一项为真实的客户端ip,剩下的就是曾经经过的代理或负载均衡的ip地址,经过几个就会出现几个。
光从定义来看, X-Forward-For只是记录了, 来自客户端所流经的代理服务器的链路路程, 好像没啥作用. 获取真实IP, 通过获取设定的X-Real-IP即可.
一般情况下, 好像是可行的, 因为是你忽略了, 你的web架构中, nginx代理只有一层. 当web架构中, 存在多层代理服务器时, 使用X-Real-IP会丢失真实的客户端IP, 而X-Forward-For依旧为你保留了真实的客户端ip, 这也为什么后端web server从X-Forward-For中获取client ip, 而不是从X-Real-IP中获取的本质原因.
tomcat配置:
在server.xml的配置中, 对于日志的输入格式和内容默认为:
注: 格式pattern="%h %l %u %t "%r" %s %b", 默认等价于pattern="common"
输出的样例结果为:
127.0.0.1 - - [17/Feb/2016:16:30:39 +0800] "GET / HTTP/1.1" 200 52
要输出前端nginx传递过来的客户端实际ip, 则需要把格式改为如下:
pattern="%{X-Forwarded-For}i %l %u %t "%r" %s %b"
这样tomcat的就能利用到新注入的header, 并输出真实客户端ip到日志中去.
webapp的修改:
在webapp中, 获取ip可以修改如下:
HttpServletRequest request = ...;
String ip = request.getHeader("X-Forwarded-For");
来代替:
HttpServletRequest request = ...;
String ip = request.getRemoteAddr();
在结合log4j的使用中, 可以借助MDC/NDC来写入ip地址:
java代码如下:
HttpServletRequest request = ...;
String ip = request.getHeader("X-Forwarded-For");
MDC.put("ip", ip);
log4j的配置如下:
log4j.appender.console.layout.ConversionPattern=[%X{ip}] -[%c]-[%p] %m%n
注意[%X{ip}] 这个自定义项.
参考博文"log4j获取IP显示在日志中".
总结:
对于该问题, 网上有很多资料. 这边重复一下, 一方为总结, 一方也觉得有所收获. 权当学习笔记.
公众号&游戏站点:
个人微信公众号: 木目的H5游戏世界
个人游戏作品集站点(尚在建设中...): www.mmxfgame.com, 也可直接ip访问: http://120.26.221.54/.