nginx获取客户端真实ip、域名、协议和端口
nginx是一种反向代理和负载均衡服务器,它不仅很轻量,还拥有高性能特性和支持插件化开发。使用者可以根据自身需求为nginx指定某款插件来增强nginx在某种特定场景下的功能或提升nginx在某种特定场景下的性能。
nginx反向代理后,servlet应用通过request.getRemoteAddr可以获取到ip(nginx的ip地址,并非真实的客户端ip地址),通过request.getRequestURL方法获取到的域名也都是nginx访问应用时的域名、协议和端口,并非真实客户端的域名、协议和端口等。
直接获取信息有哪些问题?
如下配置,本地服务器的jetty或者tomcat端口为808,nginx端口号为80,nginx反向代理8080端口
server{
listen80;
location/ {
proxy_passhttp://127.0.0.1:8080;反向代理应用服务器HTTP地址
}
}
另外一个机器B访问http://192.168.1.6/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并非机器B的ip,获取到的url是nginx配置的proxy_passs的URL地址,而非机器B浏览器上的真实地址。如果nginx作为https反向代理到后端的http服务,那么request.getRequestURL获取到URL是http前缀而非https前缀,无法获取到真实协议。如果借助request.getRequestURL获取的url用为拼接跳转地址,就会跳转到错误地址,这是nginx反向代理经常发生的问题。
如何解决这种问题?
解决问题的思考方向:1.nginx是代理服务器,客户端请求经nginx转发到jetty/tomcat,如果Nginx不把客服端真实的ip、域名、端口、协议告诉jetty/tomcat,那么jetty/tomcat将无法知道,因此要配置http header将这些信息告诉jetty/tomcat。2.jetty/tomcat不能直接获取连接它的客户端(nginx)信息,而是要从http header获取客户端的信息。
proxy_set_headerHost$http_host;
proxy_set_headerX-Real-IP$remote_addr;
proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;
proxy_set_headerX-Forwarded-Proto$scheme;
参数含义:
host:包含客户端真实的域名和端口信息
X-Real-IP:客户端真实的ip
X-Forwarded-For:这个和X-Real-IP类似,但它在多层代理时会包含真实客户端和中间每个代理服务器的ip
X-Forwarded-Proto:客户端的协议信息
nginx配置完之后,重启运行程序
RemoteAddr: 127.0.0.1
URL: http://192.168.1.6/test
我们发现获取到的url是对的,而ip还是非真实的。如果用nginx作为https服务器反向代理到http服务器,会发现浏览器地址是https前缀但是通过request.getRequestURL获取到的URL还是http前缀,说明单靠nginx配置还不能解决问题。
通过java获取客户端的信息
通过如下方式能够获取到客户端的真实ip,因为servlet api提供了request.getRemoteAddr方法获取客户端ip,所以不管是否使用反向代理对于开发者应该是透明的。
/***
* 获取客户端IP地址;这里通过了Nginx获取;X-Real-IP
*/
publicstaticStringgetClientIP(HttpServletRequest request) {
StringfromSource ="X-Real-IP";
Stringip = request.getHeader("X-Real-IP");
if(ip ==null|| ip.length() ==0||"unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
fromSource ="X-Forwarded-For";
}
if(ip ==null|| ip.length() ==0||"unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
fromSource ="Proxy-Client-IP";
}
if(ip ==null|| ip.length() ==0||"unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
fromSource ="WL-Proxy-Client-IP";
}
if(ip ==null|| ip.length() ==0||"unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
fromSource ="request.getRemoteAddr";
}
returnip;
}
jetty服务器
在jetty.xml配置文件中找到httpConfig进行配置
<Newid="httpConfig"class="org.eclipse.jetty.server.HttpConfiguration">
...
<Callname="addCustomizer">
<Arg><Newclass="org.eclipse.jetty.server.ForwardedRequestCustomizer"/>Arg>
Call>
New>
重启jetty服务器,再次访问http://192.168.1.6/test,结果如下
RemoteAddr: 192.168.1.6
URL: http://192.168.1.6/test
通过request.getRemoteAddr获取到的ip就是客户端真实ip,获取到的URL也是真实的URL,nginx作为https代理,获取到的协议也会是https。如果不想改jetty.xml配置文件,jetty提供了http-forwarder模块,所以可以直接通过命令行启动
java -jar demo.jar --module=http-forwarded
tomcat
在tomcat的server.xml配置文件的host元素配置
<ValveclassName="org.apache.catalina.valves.RemoteIpValve"/>