javaEE 获得外网的访问的ip

X-Forwarded-For的一些理解

X-Forwarded-For 是一个 HTTP 扩展头部,主要是为了让 Web 服务器获取访问用户的真实 IP 地址(其实这个真实未必是真实的,后面会说到)。

那为什么 Web 服务器只有通过 X-Forwarded-For 头才能获取真实的 IP?
这里用 PHP 语言来说明,不明白原理的开发者为了获取客户 IP,会使用 $_SERVER['REMOTE_ADDR'] 变量,这个服务器变量表示和 Web 服务器握手的 IP 是什么(这个不能伪造)。
但是很多用户都通过代理来访问服务器的,那么假如使用该全局变量,PHP获取到的 IP 就是代理服务器的 IP(不是用户的)。

可能很多人看的晕乎乎的,那么看看一个请求可能经过的路径:

客户端=>(正向代理=>透明代理=>服务器反向代理=>)Web服务器

其中正向代理、透明代理、服务器反向代理这三个环节并不一定存在。

  • 什么是正向代理呢,很多企业会在自己的出口网关上设置代理(主要是为了加速和节省流量)。
  • 透明代理可能是用户自己设置的代理(比如为了FQ,这样也绕开了公司的正向代理)。
  • 服务器反向代理是部署在 Web 服务器前面的,主要原因是为了负载均衡和安全考虑。

现在假设几种情况:

  • 假如客户端直接连接 Web 服务器(假设 Web 服务器有公网地址),则 $_SERVER['REMOTE_ADDR'] 获取到的是客户端的真实 IP 。
  • 假设 Web 服务器前部署了反向代理(比如 Nginx),则 $_SERVER['REMOTE_ADDR'] 获取到的是反向代理设备的 IP(Nginx)。
  • 假设客户端通过正向代理直接连接 Web 服务器(假设 Web 服务器有公网地址),则 $_SERVER['REMOTE_ADDR'] 获取到的正向代理设备的 IP 。

其实这里的知识点很多,记住一点就行了,$_SERVER['REMOTE_ADDR'] 获取到的 IP 是 Web 服务器 TCP 连接的 IP(这个不能伪造,一般 Web 服务器也不会修改这个头)。

X-Forwarded-For

从上面大家也看出来了,因为有了各种代理,才会导致 REMOTE_ADDR 这个全局变量产生了一定的歧义,为了让 Web 服务器获取到真实的客户端 IP,X-Forwarded-For 出现了,这个协议头也是由 Squid 起草的(Squid 应该是最早的代理软件之一)。

这个协议头的格式:

X-Forwarded-For: client, proxy1, proxy2

client 表示用户的真实 IP,每经过一次代理服务器,代理服务器会在这个头增加用户的 IP(有点拗口)。
注意最后一个代理服务器请求 Web 服务器的时候是不会将自己的 IP 附加到 X-Forwarded-For 头上的,最后一个代理服务器的 IP 地址应该通过$_SERVER['REMOTE_ADDR']获取。

举个例子:
用户的 IP 为(A),分别经过两个代理服务器(B,C),最后到达 Web 服务器,那么Web 服务器接收到的 X-Forwarded-For 就是 A,B。



/**


* @date 2018年1月2日 下午4:41:38
* @Title: getIpAddr 
* @Description: 获取外网IP
* @param request
* @return    设定文件 
* @return String    返回类型 
* @throws
*/
public static  String getIpAddr(HttpServletRequest request) {   
        String ipAddress = null;   
        InetAddress inet=null;  
        //ipAddress = request.getRemoteAddr();   
        ipAddress = request.getHeader("x-forwarded-for");   
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {   
        ipAddress = request.getHeader("Proxy-Client-IP");   
        }   
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {   
            ipAddress = request.getHeader("WL-Proxy-Client-IP");   
        }   
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {   
        ipAddress = request.getRemoteAddr();   
           if(ipAddress.equals("127.0.0.1")|| ipAddress.equals("0:0:0:0:0:0:0:1")){
           //根据网卡取本机配置的IP   
       try {   
           inet = InetAddress.getLocalHost();   
       } catch (UnknownHostException e) {   
           e.printStackTrace();   
       }   
       ipAddress= inet.getHostAddress();   
        }   
       }   
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割   
        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15   
            if(ipAddress.indexOf(",")>0){   
                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));   
            }   
        }  
        return ipAddress;    

     } 


X-Forwarded-For 安全性

那么很多同学会说,通过 X-Forwarded-For 就能获取到用户的真实 IP,是不是万事大吉了,对于 Web 服务器来说,安全有两个纬度,第一个纬度是 REMOTE_ADDR 这个头,这个头不能伪造。第二个纬度就是 X-Forwarded-For,但是这个头是可以伪造的。

那么谁在伪造呢?,我们分别看下:

正向代理一般是公司加速使用的,假如没有特殊的目的,不应该传递 X-Forwarded-For 头,因为它的上层连接是内部 IP,不应该暴露出去,当然它也可以透明的传递这个头的值(而这个值用户可以伪造)。

透明代理,这个可能是用户自己搭建的(比如FQ),而且在一个用户的请求中,可能有多个透明代理,这时候透明代理就抓瞎了,为了让自己尽量的正确,也会透明的传递这个头的值(而这个值用户可以伪造),当然一些不法企业或者人员,为了一些目的,会改下这个头的值(比如来自世界各地的 IP 地址)。

反向代理,Web 服务器前的反向代理服务器是不会伪造的(同一个公司的),一般会原样传递这个头的值。

那么对应用程序来说,既然这个值不能完全相信,该怎么办呢?这取决于应用的性质:

假如提供的服务可能就是一些非机密服务,也不需要知道用户的真实 IP,那么建议应用程序或者 Web 服务器对 REMOTE_ADDR 做一些限制,比如进行限速等等,也可以放行一些白名单的代理 IP,但是这些白名单 IP 就太难衡量了。

假设你的服务很重要,比如抽奖(一个 IP 只能一次抽奖),这时候你可能想通过 X-Forwarded-For 来获取用户的真实 IP(假如使用 REMOTE_ADDR 则会误杀一片),但是由于 X-Forwarded-For 可能会伪造,所以其实并没有什么好的办法,只能在应用层进行处理了。



猜你喜欢

转载自blog.csdn.net/yz18931904/article/details/80665350