本文基于方法 HttpServletRequest**.getHeader** 和 HttpServletRequest.getRemoteAddr 介绍如何在服务器端获取客户端真实IP地址。

# 业务背景

  服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等拦截,在进行IP限定的时候就需要获取客户端真实的IP。

# 基础知识

访问服务端的方式一般分为两种:

  • 未经过代理,直接访问服务器端;

  • 通过多级代理,最终到达服务器端(nginx,squid,haproxy)。

    客户端请求信息都包含在HttpServletRequest中,对于第一种访问方式可以通过getRemoteAddr()方法获得客户端真实IP,而另一种则行不通,但是可以通过x-forwarded-for获得转发后请求信息。当客户端请求被转发时,IP将会追加在其后并以英文逗号隔开,例如:10.47.103.13,4.2.2.2,10.96.112.230。

请求中的参数:

request.getHeader("x-forwarded-for") : 10.47.103.13,4.2.2.2,10.96.112.230
request.getHeader("X-Real-IP") : 10.47.103.13
request.getRemoteAddr():10.96.112.230

客户端访问经过转发,IP将会追加在其后并以逗号隔开。最终准确的客户端信息为:

  • x-forwarded-for 不为空,则为逗号前第一个IP ;

  • X-Real-IP不为空,则为该IP ;

  • 否则为getRemoteAddr() ;

    相关请求头的解释:

    X-Forwarded-For 记录一个请求从客户端出发到目标服务器过程中经历的代理,或者负载平衡设备的IP。这是由缓存代理软件 Squid 引入,用来表示 HTTP 请求端真实 IP,现在已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。格式为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-IP nginx代理一般会加上此请求头。

# 获取客户端真实IP地址

/**
 * 获取客户端的IP地址<br/>
 * 注意本地测试访问项目地址时,浏览器请求不要用 localhost,请用本机IP;否则,取不到 IP
 *
 * @author east7
 * @date 2019年12月03日
 * @return String 真实IP地址
 */
public static String getClientIpAddress(HttpServletRequest request) {
    // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
    String headerName = "x-forwarded-for";
    String ip = request.getHeader(headerName);
    if (null != ip && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
        // 多次反向代理后会有多个IP值,第一个IP才是真实IP,它们按照英文逗号','分割
        if (ip.indexOf(",") != -1) {
            ip = ip.split(",")[0];
        }
    }
    if (checkIp(ip)) {
        headerName = "Proxy-Client-IP";
        ip = request.getHeader(headerName);
    }
    if (checkIp(ip)) {
        headerName = "WL-Proxy-Client-IP";
        ip = request.getHeader(headerName);
    }
    if (checkIp(ip)) {
        headerName = "HTTP_CLIENT_IP";
        ip = request.getHeader(headerName);
    }
    if (checkIp(ip)) {
        headerName = "HTTP_X_FORWARDED_FOR";
        ip = request.getHeader(headerName);
    }
    if (checkIp(ip)) {
        headerName = "X-Real-IP";
        ip = request.getHeader(headerName);
    }
    if (checkIp(ip)) {
        headerName = "remote addr";
        ip = request.getRemoteAddr();
        // 127.0.0.1 ipv4, 0:0:0:0:0:0:0:1 ipv6
        if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
            //根据网卡取本机配置的IP
            InetAddress inet = null;
            try {
                inet = InetAddress.getLocalHost();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            ip = inet.getHostAddress();
        }
    }
    logger.info("getClientIp  IP is " + ip + ", headerName = " + headerName);
    return ip;
}
private static boolean checkIp(String ip) {
    if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
        return true;
    }
    return false;
}

# 内外网

除了上面的简单获取IP之外,一般的公司还会进行内网外网判断,完整示例如下,此处代码参考网络

public class IpUtil {

    /**
     * 判断IP是否是内网地址
     * @param ipAddress ip地址
     * @return 是否是内网地址
     */
    public static boolean isInnerIP(String ipAddress) {
        boolean isInnerIp;
        long ipNum = getIpNum(ipAddress);
        /**   
        私有IP:A类  10.0.0.0-10.255.255.255   
               B类  172.16.0.0-172.31.255.255   
               C类  192.168.0.0-192.168.255.255   
        还有127这个网段是环回地址   
        **/
        long aBegin = getIpNum("10.0.0.0");
        long aEnd = getIpNum("10.255.255.255");

        long bBegin = getIpNum("172.16.0.0");
        long bEnd = getIpNum("172.31.255.255");

        long cBegin = getIpNum("192.168.0.0");
        long cEnd = getIpNum("192.168.255.255");
        isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd)
                || ipAddress.equals("127.0.0.1");
        return isInnerIp;
    }

    private static long getIpNum(String ipAddress) {
        String[] ip = ipAddress.split("\\.");
        long a = Integer.parseInt(ip[0]);
        long b = Integer.parseInt(ip[1]);
        long c = Integer.parseInt(ip[2]);
        long d = Integer.parseInt(ip[3]);

        return a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
    }

    private static boolean isInner(long userIp, long begin, long end) {
        return (userIp >= begin) && (userIp <= end);
    }

    public static String getRealIP(HttpServletRequest request){
        // 获取客户端ip地址
        String clientIp = request.getHeader("x-forwarded-for");

        if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getRemoteAddr();
        }

        String[] clientIps = clientIp.split(",");
        if(clientIps.length <= 1) return clientIp.trim();

        // 判断是否来自CDN
        if(isComefromCDN(request)){
            if(clientIps.length>=2) return clientIps[clientIps.length-2].trim();
        }

        return clientIps[clientIps.length-1].trim();
    }

    private static boolean isComefromCDN(HttpServletRequest request) {
        String host = request.getHeader("host");
        return host.contains("www.189.cn") ||host.contains("shouji.189.cn") || host.contains(
                "image2.chinatelecom-ec.com") || host.contains(
                "image1.chinatelecom-ec.com");
    }
}