记一次nginx取客户端真实ip的经历

1. what happened

因为需要,所以选择了用nginx做负载均衡,但是在请求转发到后端节点的时候,客户端真实ip却变成了nginx节点的ip。这让很多业务直接崩掉,比如说websocket。因此,开始了这么一条狗血的道路。

注:项目采用的是spring boot全家桶

2. how to do

反向代理的时候由于请求都是转发的,所以为了将实际请求的ip真实地传递到后端节点,通常用的都是加额外的http header来实现的。而在nginx里已经集成了这些功能,直接在location里用proxy_set_header就可以了,一般使用的就是X-Forwarded-Proto,X-Real-IP,X-Forwarded-For这几个头部,具体如下:

server { ... location xxx { proxy_pass xxx proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $schema; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

加上这些东西,nginx就会在转发请求的时候自动的把那几个header给填进去。当然,你的后端需要允许这样的头部才可以。

spring boot 里对于反向代理过来的请求去拿ip挺简单的,你只需要在配置文件application.properties里加上这些

server.use-forward-headers=true server.tomcat.remote-ip-header=X-Real-IP server.tomcat.protocol-header=X-Forwarded-For

它就可以工作了。

3. but

可是童话故事都是骗人的,为什么我照着搞我还是拿不到ip?而且直到现在还没搞明白,配那三个玩意在哪生效的。

后来,我在google的过程查到了tomcat里提供了一个过滤器来搞这个工作,那就是

org.apache.catalina.filters.RemoteIpFilter

那些人们告诉我,你只需要把它加上就OK了,然后我屁颠屁颠地加上了,在本地测了一下,emmm果然好使,google诚不欺我。然后我就乐呵呵地把代码部到了测试服务器上,然后看了一下数据,WTF?为什么还是nginx节点的ip?我刚刚不是还在本机跑的好好的嘛!

刚开始我以为linux下的nginx转发到linux的tomcat时把那几个http header 弄没了?然后用tcpdump抓了一下包,发现并没有出问题,然后看了一下tomcat的日志(debug log真好使啊),那几个头部确实传到了,但是remoteAddr就是不对。

这尼玛简直了!然后想到会不会是网段问题?因为虽然本机和测试服务器在一个大的局域网内,但是属于不同的网段。想到这个我对比了一下测试服务器的日志和本机的日志,仔细一看,发现问题了。测试服务器的请求压根没有被RemoteIpFilter过滤,然后我看了一下RemoteIpFilter的源码,果然找到了问题的根源,RemoteIpFilter里在过滤请求的时候会先对请求的remoteAddr进行匹配,匹配规则如下

private Pattern internalProxies = Pattern.compile( "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}");

我一仔细一瞅,emmm正好nginx节点的ip就没匹配上,然后重新set一个规则就ok了。

至此,问题圆满解决。问题是,spring boot配的那三个到底干了什么嘛!