Java从代理服务器中获取客户端真实IP的方法

概要

一般在单机版的tomcat获取请求来源的ip,可以使用下面方式这么干,下文的request是HttpServletRequest对象。

    String remoteAddr = request.getRemoteAddr();

但是如果了使用反向代理例如niginx,由于对客户请求的代理,此时request.getRemoteAddr()获取要么是本级地址
127.0.0.1 要么就是192.168.x.x这个内网地址,因为这种获取Ip的方式仅限与客户端和服务器端直接通信的方式才有效。在知乎上看到了一个很形象的反向代理图,如下所示:
Java从代理服务器中获取客户端真实IP的方法
反向代理对服务器做了代理,客户端是和代理进行通信。所以这种获取ip地址的信息获取到的是内网ip地址。
在nginx反向代理中,在nginx.conf 我们常常可以看到如下配置:X-Real-IP,X-Forwarded-For.

   location / {
      root   html;
      index  index.html index.htm index.jsp;
      #proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_pass http://192.168.1.224;
    } 

由于客户端和服务器端中间有个代理层的存在,所以服务器无法直接拿到客户真实的ip地址,在代理转发客户请求的时候,请求头(即Header)增加了X-FORWARDED-FOR这个属性,这个属性是追踪原来客户端和服务器端的IP地址。只有通过了Http代理或者负载均衡服务器才会有这个属性。当客户端发起请求时,将会有以下5步操作。

  1. dns域名解析,通过域名找到ip
  2. 代理服务器的TCP监听端口,nginx默认80端口,当有TCP连接后,将客户端请求经过处理发送到服务器
  3. 服务器对接收到的Http数据包进行解压,解密,创建会话并处理请求
  4. 服务器处理和相应请求后,将相应转发给代理服务器
  5. 代理服务器将服务端的响应在转发给客户端服务器

所以整个请求中,客户端并没有和服务器端直接进行通信,所以request.getRemoteAddr()获取的是代理服务器的地址,因为服务器接收的是代理服务器发起的请求。
所以可以用下面方式获取客户端IP地址

获取Ip调用的方法

    /**
     * 获取IP地址
     *
     */
    public static String getIpAddr(HttpServletRequest request)
    {
        String ip = request.getHeader("X-Real-IP");
        if(!StringUtils.isBlank(ip) && !"unknown".equalsIgnoreCase(ip))
            return ip;
        ip = request.getHeader("X-Forwarded-For");
        if(!StringUtils.isBlank(ip) && !"unknown".equalsIgnoreCase(ip))
        {
            int index = ip.indexOf(',');
            if(index != -1)
                return ip.substring(0, index);
            else
                return ip;
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
            ip = request.getHeader("Proxy-Client-IP");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
            ip = request.getHeader("WL-Proxy-Client-IP");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
            ip = request.getHeader("HTTP_CLIENT_IP");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
            ip = request.getRemoteAddr();
        return ip;
    }
  • 相关请求头解释
String ip = request.getHeader("X-Real-IP");

X-Real-IP是Nginx用来获取用户的真实ip地址,但是有时候可能获取不到返回是UNKONWN。所以当获取不到使用下面方式进行获取:

String ip = request.getHeader("X-Forwarded-For");

X-Forwarded-For 有时候可能获取不到,因为服务器不一定使用了反向代理,所以可能返回的也是UNKONWN,获取不到可以使用以下方式进行获取:

String ip = request.getHeader("Proxy-Client-IP");

Proxy-Client-IP 是apache服务器定制一个请求头,这个属性只在apache服务器才可以看到。所以当服务器不是apache服务器,也会返回UNKONWN

String ip = request.getHeader("WL-Proxy-Client-IP");

WL- Proxy-Client-IP 其中WL是weblogic的缩写,这通常用户weblogic服务器环境中,也可能返回UNKONWN

String ip = request.getHeader("HTTP_CLIENT_IP");

HTTP_CLIENT_IP 有些代理服务器会加上此配置

String ip = request.getHeader("HTTP_X_FORWARD_FOR");

HTTP_X_FORWARD_FOR 可以在nginx配置此项,一般为 HTTP_X_FORWARD_FOR $remote_addr;
看了上面的相关头解释和获取IP的方法,则可以将其集成到我们的项目中,如下采用webService接口获取IP和基于Spring MVC方式获取IP

webService获取IP

下面以scala定一个webService接口

  @Path("/webService/getIp")
  @POST
  def getClientIp(@Context httpServletRequest: HttpServletRequest): String

实现类:

   // 获取客户端的ip
override def getClientIp(@Context httpServletRequest: HttpServletRequest): String = {
  		var ip =""
        Try({
         	ip = StringUtils.defaultIfBlank(getRemoteIP(httpServletRequest), "unknown")
          	println("获取ip="+ip)
        }) match {
          case Success(_) => ip
          case Failure(e) => {
           throw e
          }
      }
  }

SpringMVC获取IP

@PostMapping(Array("/springMvc/getIp"))
  def getIp(httpRequest: HttpServletRequest): String = {
  		var ip =""
     Try({
		ip = StringUtils.defaultIfBlank(getRemoteIP(httpServletRequest), "unknown")
          	println("获取ip="+ip)
        }) match {
          case Success(response) => ip
          case Failure(e) => throw e
      }
  }

所以使用SpringMvc方式获取ip还是webService可以使用上面的案列,非常简单。