一、问题背景

nginx server_addr 不正确 nginx remoteaddr_remoteAddr

通过nginx来反向代理客户端请求,经过nginx中转转发给tomcat服务器,但发现tomcat服务器无法获取到正确的remoteAddr客户端地址,每次请求拿到的都是nginx所在服务器的IP

1、在tomcat服务器上查看tomcat服务日志,发现打印的请求日志,remoteAddr都是nginx的IP,而不是客户端的真实IP

## 192.168.200.251是nginx负载均衡服务器的地址
15:08:12 INFO    [tid:166453,Q:W228AFjoBcRQIzvVzOuJ] [L-Android]func=xxxx,remote=192.168.200.251

二、定位过程

1、nginx配置如下

location /myService {
                   proxy_set_header    X-FORWARDED-FOR $remote_addr;
                   proxy_set_header    X-Forwarded-Proto $scheme;
                   proxy_set_header    Host   $http_host;

                   #分发到tomcat服务器,192.168.201.228为tomcat服务器
                   proxy_pass http://192.168.201.228:9900;
               }

一开始以为是nginx配置有问题,设置了各种参数后,发现不是nginx问题,而是tomcat 的设置影响,在tomcat的conf/server.xml里RemoteIpValve项的InternalProxies

<Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost" appBase="webapps"
            unpackWARs="false" autoDeploy="true"
        <Valve className="org.apache.catalina.valves.AccessLogValve"
        <!-- for nginx -->
        <Valve className="org.apache.catalina.valves.RemoteIpValve"
                 remoteIpHeader="X-FORWARDED-FOR"
                 protocolHeader="X-FORWARDED-PROTO"
                 internalProxies="192\.168\.*"/>
       </Host>
</Engine>

在使用Nginx代理网络请求时,设置proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for,但是在我的server端(Tomcat,Spring)收到的网络请求中却得不到这个x-forwarded-for的信息,原因是我的代理机器的IP地址与Tomcat RemoteIpValve中配置的默认内部代理地址internalProxies能匹配,于是x-forwarded-for信息被忽略并删除了。
(在server.xml中删除RemoteIpValve的默认配置,或者配置internalProxies为其他地址(如server的地址),就可以在x-forwarded-for中得到代理传输过来的信息。)

三、解决方案

1、将tomcat server.xml的internalProxies修改为nginx的IP,tomcat即可正确处理x-forwarded-for逻辑,获取正确的remoteAddr

<Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost" appBase="webapps"
            unpackWARs="false" autoDeploy="true"
        <Valve className="org.apache.catalina.valves.AccessLogValve"
        <!-- for nginx -->
        <Valve className="org.apache.catalina.valves.RemoteIpValve"
                 remoteIpHeader="X-FORWARDED-FOR"
                 protocolHeader="X-FORWARDED-PROTO"
                 ## 修改internalProxies为nginx的IP
                 internalProxies="127\.0\.0\.1|192\.168\.200\.251"/>
       </Host>
</Engine>

2、重启tomcat,查看客户端请求日志,可以看到remoteAddr已经是客户端的真实IP而不是nginx的IP了

16:08:12 INFO    [tid:166453,Q:W228AFjoBcRQIzvVzOuJ] [L-Android]func=xxxx,remote=192.168.201.46

四、tomcat RemoteIpValve InternalProxies原理解析

X-Forwarded-For
x-forwarded-for在HTTP Header中,用来记录代理链的IP信息(不包括最后一次的代理)。

RemoteIpValve
Tomcat中的RemoteIpValve的设计意图是根据InternalProxies和TrustedProxies的配置,来过滤代理信息链X-Forwarded-For中的信息,获取代理机器之前的IP地址,并改写RemoteAddress的值。如果没有RemoteIpValve的处理,且有代理转发的情况下,则RemoteAddress永远为代理机器的IP地址,而不是代理机器之前发送数据包机器的IP地址(根据网络情况,可能为实际的用户IP地址)
具体逻辑如下图所示。

nginx server_addr 不正确 nginx remoteaddr_internalProxies_02

初始RemoteAddress为代理机器的IP地址,首先判断是否与InternalProxies匹配,如果不匹配,则RemoterIpValve不会做处理;如果匹配,则根据InternalProxies和TrustedProxies的配置来决定是否改写X-Forwarded-For和RemoteAddress以及X-Forwarded-By。

internalProxies默认情况下是有配置值的(貌似是常见的局域网IP地址),trustedProxies默认为null。而我的内网代理机器和我本机的IP地址都与InternalProxies匹配,则X-Forwarded-For中代理链的信息被删除。

参考:

  1. Tomcat 8的RemoteIpValve源码(Valve是阀门的意思),核心处理逻辑是invoke函数
  2. RemoteIpValve文档