问题描述

通常服务端用request获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的,除了服务端通过了nginx, Apache, Squid等反向代理软件这种情况就不能获取到客户端的真实IP地址了。

使用反向代理服务器时,访问服务器是反向代理服务器访问的,反向代理服务器再将得到的结果返回给真实的请求方。所以这种情况直接使用request.getRemoteAddr();这个方法得到的是代理工具的ip地址, 并不是真正的请求方的地址。

例如,当我们访问http://www.abc.com/index.jsp/时,其实并不是我们浏览器真正访问到了服务器上的index.jsp文件,而是先由代理服务器去访问http://192.168.1.110:2046/index.jsp,代理服务器再将访问到的结果返回给我们的浏览器,因为是代理服务器去访问index.jsp的,所以index.jsp中通过request.getRemoteAddr()的方法获取的IP实际上是代理服务器的地址,并不是客户端的IP地址。

分析

经过代理以后,由于在客户端和服务之间增加了中间层,因此服务器无法直接拿到客户端的IP,服务器端应用也无法直接通过转发请求的地址返回给客户端。但是在转发请求的HTTP头信息中,增加了X-FORWARDED-FOR信息, 用以跟踪原有的客户端IP地址和原来客户端请求的服务器地址。

解决方法

请注意,X-Forwarded-For通常是逗号分隔的ip列表,链中的每个代理都会将它看到的远程地址添加到列表中, 多个IP按照','分割。如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, 192.168.1.100用户真实IP为: 192.168.1.110

private static final String[] IP_HEADER_CANDIDATES = {
        "X-Forwarded-For",
        "Proxy-Client-IP",
        "WL-Proxy-Client-IP",
        "HTTP_X_FORWARDED_FOR",
        "HTTP_X_FORWARDED",
        "HTTP_X_CLUSTER_CLIENT_IP",
        "HTTP_CLIENT_IP",
        "HTTP_FORWARDED_FOR",
        "HTTP_FORWARDED",
        "HTTP_VIA",
        "REMOTE_ADDR"
    };

public static String getIpAddress(HttpServletRequest request) {
	for (String header: IP_HEADER_CANDIDATES) {
            String ipList = request.getHeader(header);
            if (ipList != null && ipList.length() != 0 && !"unknown".equalsIgnoreCase(ipList)) {
                String ip = ipList.split(",")[0];
                return ip;
            }
        }
   return request.getRemoteAddr();
}

关于上述请求头的说明:

  1. 这些请求头都不是http协议里的标准请求头,也就是说这个是各个代理服务器自己规定的表示客户端地址的请求头。
  2. 这些请求头不是代理服务器一定会带上的,网络上的很多匿名代理就没有这些请求头,所以获取到的客户端ip不一定是真实的客户端ip。代理服务器一般都可以自定义请求头设置。
  3. 即使请求经过的代理都会按自己的规范附上代理请求头,上面的代码也不能确保获得的一定是客户端ip。不同的网络架构,判断请求头的顺序是不一样的。
  • X-Forwarded-For 这是一个 Squid 开发的字段,只有在通过了HTTP代理或者负载均衡服务器时才会添加该项。
    格式为X-Forwarded-For:client1,proxy1,proxy2,一般情况下,第一个ip为客户端真实ip,后面的为经过的代理服务器ip。现在大部分的代理都会加上这个请求头。
  • Proxy-Client-IP/WL- Proxy-Client-IP 这个一般是经过apache http服务器的请求才会有,用apache http做代理时一般会加上Proxy-Client-IP请求头,而WL-Proxy-Client-IP是他的weblogic插件加上的头。
  • HTTP_CLIENT_IP 有些代理服务器会加上此请求头。
  • X-Real-IPnginx代理一般会加上此请求头。

参考

How to extract IP Address in Spring MVC Controller get call?Java获取客户端真实IP地址的两种方法干货:Java正确获取客户端真实IP方法整理