• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

Tomcat/Jetty + Nginx(配置反向代理)获取客户端真实IP、域名、协议、端口

JAVA web前端中文站 2年前 (2017-10-12) 818次浏览 已收录 0个评论

Nginx 反向代理后,Servlet 应用通过 request.getRemoteAddr()取到的 IP 是 Nginx 的 IP 地址,并非客户端真实 IP,通过 request.getRequestURL()获取的域名、协议、端口都是 Nginx 访问 Web 应用时的域名、协议、端口,而非客户端浏览器地址栏上的真实域名、协议、端口。

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

例如在某一台 IP 为 10.4.64.22 的服务器上,Jetty 或者 Tomcat 端口号为 8080,Nginx 端口号 80,Nginx 反向代理 8080 端口:

 server {     listen 80;     location / {         proxy_pass http://127.0.0.1:8080; # 反向代理应用服务器 HTTP 地址     } }

在另一台机器上用浏览器打开 http://10.4.64.22/test 访问某个 Servlet 应用,获取客户端 IP 和 URL:

 System.out.println("RemoteAddr: " + request.getRemoteAddr()); System.out.println("URL: " + request.getRequestURL().toString());

结果是:

 RemoteAddr: 127.0.0.1 URL: http://127.0.0.1:8080/test

可以发现,Servlet 程序获取到的客户端 IP 是 Nginx 的 IP 而非浏览器所在机器的 IP,获取到的 URL 是 Nginx proxy_pass 配置的 URL 组成的地址,而非浏览器地址栏上的真实地址。如果将 Nginx 用作 https 服务器反向代理后端的 http 服务,那么 request.getRequestURL()获取的 URL 是 http 前缀的而非 https 前缀,无法获取到浏览器地址栏的真实协议。如果此时将 request.getRequestURL()获取得到的 URL 用作拼接 Redirect 地址,就会出现跳转到错误的地址,这也是 Nginx 反向代理时经常出现的一个问题。

问题产生的原因

Nginx 的反向代理实际上是客户端和真实的应用服务器之间的一个桥梁,客户端(一般是浏览器)访问 Nginx 服务器,Nginx 再去访问 Web 应用服务器。对于 Web 应用来说,这次 HTTP 请求的客户端是 Nginx 而非真实的客户端浏览器,如果不做特殊处理的话,Web 应用会把 Nginx 当作请求的客户端,获取到的客户端信息就是 Nginx 的一些信息。

解决方案

解决这个问题要从两个方面来解决: 

  1. 由于 Nginx 是代理服务器,所有客户端请求都从 Nginx 转发到 Jetty/Tomcat,如果 Nginx 不把客户端真实 IP、域名、协议、端口告诉 Jetty/Tomcat,那么 Jetty/Tomcat 应用是永远不会知道这些信息的,所以需要 Nginx 配置一些 HTTP Header 来将这些信息告诉被代理的 Jetty/Tomcat; 
  2. Jetty/Tomcat 这一端,不能再傻乎乎的获取直接和它连接的客户端(也就是 Nginx)的信息,而是要从 Nginx 传递过来的 HTTP Header 中获取客户端信息。

Nginx

添加以下配置:

 proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;

解释以下上面的配置,以上配置是在 Nginx 反向代理的时候,添加一些请求 Header。 

  1. Host 包含客户端真实的域名和端口号; 
  2. X-Forwarded-Proto 表示客户端真实的协议(http 还是 https); 
  3. X-Real-IP 表示客户端真实的 IP; 
  4. X-Forwarded-For 这个 Header 和 X-Real-IP 类似,但它在多层代理时会包含真实客户端及中间每个代理服务器的 IP。

再试一下 request.getRemoteAddr()和 request.getRequestURL()的输出结果:

 RemoteAddr: 127.0.0.1 URL: http://10.4.64.22/test

可以发现 URL 好像已经没问题了,但是 IP 还是本地的 IP 而非真实客户端 IP。但是如果是用 Nginx 作为 https 服务器反向代理到 http 服务器,会发现浏览器地址栏是 https 前缀但是 request.getRequestURL()获取到的 URL 还是 http 前缀,也就是仅仅配置 Nginx 还不能彻底解决问题。

Jetty/Tomcat

如果你在网上搜索“Java 如何获取客户端真实 IP”,搜索到的解决方案大多是通过获取 HTTP 请求头 request.getHeader("X-Forwarded-For")或 request.getHeader("X-Real-IP")来实现,也就是上面在 Nginx 上配置的 Header,这种方案获取的结果的确是正确的,但是我个人觉得并不优雅。因为既然 Servlet API 提供了 request.getRemoteAddr()方法获取客户端 IP,那么无论有没有用反向代理对于代码编写者来说应该是透明的。下面介绍一种更加优雅的方式。

Jetty

在 Jetty 服务器的 jetty.xml 文件中,找到 httpConfig,加入配置:

 <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration"> <!-- 省略其他配置 -->   <Call name="addCustomizer">     <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>   </Call> </New>

重新启动 Jetty,再用浏览器打开 http://10.4.64.22/test 测试,结果:

 RemoteAddr: 10.1.3.7 URL: http://10.4.64.22/test

此时可发现通过 request.getRemoteAddr()获取到的 IP 不再是 127.0.0.1 而是客户端真实 IP,request.getRequestURL()获取的 URL 也是浏览器上的真实 URL,如果 Nginx 作为 https 代理,request.getRequestURL()的前缀也会是 https。

另外,Jetty 将这个功能封装成一个模块:http-forwarded。如果不想改 jetty.xml 配置文件的话,也可以启用 http-forwarded 模块来实现。

例如可以通过命令行启动 Jetty:

 java -jar start.jar --module=http-forwarded

Tomcat

和 Jetty 类似,如果使用 Tomcat 作为应用服务器,可以通过配置 Tomcat 的 server.xml 文件,在 Host 元素内最后加入:

 <Valve className="org.apache.catalina.valves.RemoteIpValve" />

参考资料

【注:本文源自网络文章资源,由站长整理发布】


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Tomcat/Jetty + Nginx(配置反向代理)获取客户端真实 IP、域名、协议、端口
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址