你用Java获取真实的请求IP地址姿势对不对?

116人浏览 / 0人评论 / 添加收藏

要想获取用户请求的IP地址,代码肯定不是自己手写,有经验的老司机肯定百度一下😊,然后大家能看到这段代码:

public String getIpAddr(HttpServletRequest request) {
    String ip = request.getHeader("x-forwarded-for");
    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.getRemoteAddr();
    }
    return ip;
}

这段代码有用吗?大家注意看看前面几个值都是从header头里面取得,那么作为老司机能不能篡改?肯定可以的啦。例如:
 

$.ajax({
    type : "GET",
    headers : {"X-Forwarded-For":randomIp,"WL-Proxy-Client-IP":randomIp},
    contentType : 'application/x-www-form-urlencoded;charset=utf-8',
    url : url,
    data:params,
    dataType : "text",
    success : function(data) {
        console.log(data);
     }
});

其中headers属性X-Forwarded-ForWL-Proxy-Client-IP不就是被更改了吗?

当然,现在很多都会用反向代理服务器,nginx配置如下配置一般如下:

#Nginx 设置
location  ~  ^/static {
proxy_pass  ....;
proxy_set_header X-Forward-For $remote_addr ;
}

这段配置做的事情是把X-Forward-For值设置为remote_addr,再将X-Forward-For的值传递给后面的服务。

众所周知TCP/IP建立连接时需要三次握手的,并且只有知道了client端请求的IP地址,server端的数据才能返回给client,所以client想要获取到数据就必须提供真实的IP,而request.getRemoteAddr()获取的就是用户最真实的IP。 那么为什么不直接使用使用request.getRemoteAddr()这个方法呢?

因为出于安全原因,大多数的服务都使用代理服务器(如Nginx,代理服务器是用户和服务器之间的中介)屏蔽掉用户请求和真实服务器直接通信,用户对代理服务器发起的HTTP请求,代理服务器对服务集群中的真实部署的对应服务进行“二次请求”,所以最终获取的IP是代理服务器的ip地址,如192.168.xx.xx/127.xx.xx.xx等等。

所以在使用了反向代理的情况下,request.getRemoteAddr()获取的是反向代理的ip地址。所以在反向代理配置中将X-Forward-For替换为remote_addr真实的IP地址。之后在内部服务中获取的x-forwarded-for便是真实的ip地址了。


给出一个解决方法的Java代码:

public class IpUtils {
    public static final String _255 = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
    public static final Pattern pattern = Pattern.compile("^(?:" + _255 + "\\.){3}" + _255 + "$");

    public static String longToIpV4(long longIp) {
        int octet3 = (int) ((longIp >> 24) % 256);
        int octet2 = (int) ((longIp >> 16) % 256);
        int octet1 = (int) ((longIp >> 8) % 256);
        int octet0 = (int) ((longIp) % 256);
        return octet3 + "." + octet2 + "." + octet1 + "." + octet0;
    }

    public static long ipV4ToLong(String ip) {
        String[] octets = ip.split("\\.");
        return (Long.parseLong(octets[0]) << 24) + (Integer.parseInt(octets[1]) << 16)
                + (Integer.parseInt(octets[2]) << 8) + Integer.parseInt(octets[3]);
    }

    public static boolean isIPv4Private(String ip) {
        long longIp = ipV4ToLong(ip);
        return (longIp >= ipV4ToLong("10.0.0.0") && longIp <= ipV4ToLong("10.255.255.255"))
                || (longIp >= ipV4ToLong("172.16.0.0") && longIp <= ipV4ToLong("172.31.255.255"))
                || longIp >= ipV4ToLong("192.168.0.0") && longIp <= ipV4ToLong("192.168.255.255");
    }

    public static boolean isIPv4Valid(String ip) {
        return pattern.matcher(ip).matches();
    }

    public static String getIpFromRequest(HttpServletRequest request) {
        String ip;
        boolean found = false;
        if ((ip = request.getHeader("x-forwarded-for")) != null) {
            StrTokenizer tokenizer = new StrTokenizer(ip, ",");
            while (tokenizer.hasNext()) {
                ip = tokenizer.nextToken().trim();
                if (isIPv4Valid(ip) && !isIPv4Private(ip)) {
                    found = true;
                    break;
                }
            }
        }
        if (!found) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

相应的对Nginx配置部分还需要设置:

location  ~  ^/static {
proxy_pass  ....;
proxy_set_header X-Forward-For $remote_addr ;
}

 

有人会发现,为啥我这么设置了,好像获取的IP地址还是有偏差,当然,还有种情况,代码是不好处理的,这就是这种场景:

如果用户请求本身也有代理服务器,那么就变成下面这种情况:

这种情况下是无解的,业务服务代理服务器是获取不到用户真实的IP地址的:)😌

全部评论