现在的位置: 首页 > 综合 > 正文

获得用户真实IP的四种方法

2017年11月28日 ⁄ 综合 ⁄ 共 2830字 ⁄ 字号 评论关闭

在web开发时有时需要验证用户的真实IP,在Java开发中,使用request.getRemoteAddr() 得到的可能是request路经的代理(Proxy)或者负载平衡器(Load Balancer)的IP,而并非用户的真实IP。一般地,Proxy或者Load Balancer在转发一个请求时,会将原有的IP加在http request的X-FORWARDED-FOR header中,如果该header已经存在,proxy会将收到的IP添加到已有的value中,比如

X-FORWARDED-FOR: 10.4.16.159, 10.4.16.180

如果每个Proxy或Load Balancer都把它们收到的原始IP添加到X-FORWARDED-FOR header中,request的原始IP就是10.4.16.159,而不是request.getRemoteAddr() 得到的IP值。

无疑得到Proxy或者Load Balancer的IP是毫无意义的,那么怎么得到用户的原始IP呢?本文总结出四种方法:

使用前三种方式,都可以使开发者调用request.getRemoteAddr() 就能得到用户的IP。本着代码应主要服务于业务逻辑的原则,推荐尽可能使用前两种方式。第三中方式次之,第四种是特殊需求下的无奈选择了。

四种方法具体用法简要介绍如下:

配置Apache mod_remoteip模块

该模块发布在Apache v2.3,启用该模块后,配置如下指令:

RemoteIPHeader X-Forwarded-By 
RemoteIPTrustedProxy 10.0.2.16/28  
RemoteIPTrustedProxy proxy.example.com 

表示该将从X-Forwarded-For Header中提取用户的真实IP。该Header里可能包含多个IP或者域名,比如:

X-FORWARDED-FOR: 10.4.16.159, 10.4.16.180,proxy.example.com 

该模块会对Header的值从右向左扫描,如果遇到因为proxy.example.com是可信代理,10.4.16.180不可信,那么该module会把10.4.16.180值为用户真实IP。如果所有的IP都可信,10.4.16.159将被值为用户真实IP。 

注:如果不配置RemoteIPTrustedProxy,则所有代理都是可信的。

 配置Tomcat RemoteIpValve组件

在server.xml里添加如下配置:

<Valve 
   className="org.apache.catalina.valves.RemoteIpValve"
   internalProxies="192\.168\.0\.10, 192\.168\.0\.11"
   remoteIpHeader="x-forwarded-for"
   remoteIpProxiesHeader="x-forwarded-by"
   trustedProxies="192\.168\.0\.13, 192\.168\.0\.12"/>

与Apache remoteip模块类似,该配置会使Tomcat从x-forwarded-for header中读取IP列表,按从左向右的顺序扫描各个IP,规则如下:  

  1. 如果IP列表中当前的IP或域名与internalProxies中的IP或域名匹配,该IP或域名被删去,处理下个IP;  

  2. 如果IP列表中当前的IP或域名与truestedProxies中的IP或域名匹配,该IP或域名被添加在remoteIpProxiesHeader中,处理下个IP;  
  3. 如果IP列表中当前的IP或域名与truestedProxies中的IP或域名不匹配,该IP就被置为用户IP,循环结束。 

注:internalProxies,trustedProxies的值为正则表达式,如果信任所有IP,可值为“.*”

 通过Application级别的Filter(比如XForwardedFilter

该方式在web application中添加Filter,使用Filter获取x-forwarded-for Header中,按照自己的策略从中选择可能的用户IP。XForwardedFilter是比较常用的实现,将被集成在Apache Tomcat 7中,配置方式与一般的Filter相同,可配置的属性的用法也与Tomcat RemoteIpValve相同。代码示例如下:

 <filter>
    <filter-name>XForwardedFilter</filter-name>
    <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
    <init-param>
       <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value>
    </init-param>
    <init-param>
       <param-name>trustedProxies</param-name><param-value>proxy1, proxy2</param-value>
    </init-param>
 </filter>
 
 <filter-mapping>
    <filter-name>XForwardedFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
 </filter-mapping>

通过Application代码从Request Header中获取

最不推荐的一种方式,把业务逻辑无关的代码侵入到应用代码中去。通过判断x-forwarded-for Header是否存在,并且根据自身对可信代理及内部代理的策略,参考以上解释的常规做法,自己实现吧。  

抱歉!评论已关闭.