nginx+tomcat集群配置(3)---获取真实客户端IP


前言:
  在初步构建的nginx+tomcat服务集群时, 发现webserver获取到的客户端ip都是同一个, 皆为作为反向代理服务的nginx所在的机器IP. 这不太符合我们的基本需求, 为将来的数据挖掘和分析带来了麻烦.
  不过不用担心, 本文将简单介绍其背后的原因和原理, 以及具体的解决方案, ^_^.

原因分析:
  webserver在获取客户端ip时, 默认的方式是通过request.getRemoteAddr(), 这种方式本质是从直连的socket中获取到的.
  因此客户端直连web server服务器, 则获取到的ip即为真实的client ip地址信息. 不过若通过代理, 则直连的ip会被代理服务器的ip所替代.
  nginx+tomcat集群配置(3)---获取真实客户端IP
  通过图中的对比, 我们可以清楚的观察到, 在反向代理模式下, 客户端的socket已经被nginx的socket所代替. 若还是按默认的方式来获取客户端ip, 将失去意义.

解决思路:
  nginx是7层代理, 不是lvs的4层代理, 因此不可能在tcp/ip这层做手脚. 只能在http/https这个应用层协议中做文章.
  事实上, 其解决方案是基于约定的方案, 需要各方的配合来完成并实现.
  nginx的策略是: 往http/https请求中, 添加额外的header信息, 以此来完成真实客户端ip的信息传递.
  nginx+tomcat集群配置(3)---获取真实客户端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中获取的本质原因.
  nginx+tomcat集群配置(3)---获取真实客户端IP

tomcat配置:
  在server.xml的配置中, 对于日志的输入格式和内容默认为:

<!-- Access log processes all example.
    Documentation at: /docs/config/valve.html
    Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
  prefix="localhost_access_log" suffix=".txt"
  pattern="%h %l %u %t &quot;%r&quot; %s %b" />

  注: 格式pattern="%h %l %u %t &quot;%r&quot; %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 &quot;%r&quot; %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游戏世界
  nginx+tomcat集群配置(3)---获取真实客户端IP
  个人游戏作品集站点(尚在建设中...): www.mmxfgame.com,  也可直接ip访问http://120.26.221.54/.