Java獲取HttpServletRequest真實的調用ip

有時候咱們須要獲取Http請求的源IP,但因爲有着各類代理,與反向代理,還有代理請求頭標準的缺失,致使咱們想拿到真正的ip變得更加困難。這篇文章來總結下一個目前可行的比較全面的通用方法。web

首先,真實調用的ip,應該不是內網ip,而且考慮到客戶端多樣性,咱們從通用的Header出發,並也考慮各類常見客戶端的自定義Header。正則表達式

驗證IP有效

有效ip範圍是,1.0.0.0~255.255.255.255;這個網上能夠找到不少正則表達式,可是或多或少的有些不全面,例若有的正則表達式認爲"0.0.0.0"也是有效ip。聯想到其實能夠經過數值轉換實現這個判斷:判斷.分割的一共有四個字符串,每一個字符串都是數字而且第一個在1~255之間,後面三個在0~255之間,也能夠實現,咱們對比下速度,分別執行JMH程序:apache

正則表達式版本服務器

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


@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void testPattern() {
    for (int i = 0; i < 1000000; i++) {
        pattern.matcher("").matches();
        pattern.matcher("172.14.156.1").matches();
        pattern.matcher("x.14.156.1").matches();
        pattern.matcher("0.14.156.1").matches();
        pattern.matcher("1.14.156.1").matches();
        pattern.matcher("001.14.156.1").matches();
        pattern.matcher("192.14.156.1145").matches();
    }
}

分段判斷版本:性能

public static boolean validIp(String ip) {
    String[] split = ip.split("\\.");
    if (split.length != 4) {
        return false;
    }
    try {
        long first = Long.valueOf(split[0]);
        long second = Long.valueOf(split[1]);
        long third = Long.valueOf(split[2]);
        long fourth = Long.valueOf(split[3]);
        return first < 256 && first > 0
                && second < 256 && second >= 0
                && third < 256 && third >= 0
                && fourth < 256 && fourth >= 0;
    } catch (NumberFormatException e) {
        return false;
    }
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void testParse() {
    for (int i = 0; i < 1000000; i++) {
        pattern.matcher("").matches();
        pattern.matcher("172.14.156.1").matches();
        pattern.matcher("x.14.156.1").matches();
        pattern.matcher("0.14.156.1").matches();
        pattern.matcher("1.14.156.1").matches();
        pattern.matcher("001.14.156.1").matches();
        pattern.matcher("192.14.156.1145").matches();
    }
}

正則表達式結果:插件

Benchmark            Mode  Cnt     Score      Error  Units
BenchMark.testParse  avgt    5  4184.071 ± 3029.729  ms/op

分段判斷結果:代理

Benchmark              Mode  Cnt     Score      Error  Units
BenchMark.testPattern  avgt    5  3814.377 ± 2835.809  ms/op

分段版本在這裏看上去好一些,可是實際其實可能相差不大的。反正證實,這種樸素的分段判斷方法,性能上沒有太大問題。code

驗證非內網Ip

在驗證已是有效IP的基礎上,實現判斷是不是內網IP有三種方法:orm

  • 正則表達式
  • 分段判斷
  • 分段移位判斷(第一位左移24+第二位左移16+第三位左移8+第四位,看範圍是否在內網ip範圍)

在判斷已是IP的基礎上,這三種方法都會簡化不少,咱們這裏採用分段判斷。blog

獲取IP

可能的Header(HTTP Header不區分大小寫):

  • x-forwarded-for:這個是通用的代理Header,通常是逗號分割的多個ip,第一個通常是真實ip
  • x-real-ip: Nginx代理通常會填寫這個Header,標註真實的ip
  • Proxy-Client-IPWL- Proxy-Client-IP:這個通常是通過apache http服務器的請求才會有,用apache http作代理時通常會加上Proxy-Client-IP請求頭,而WL- Proxy-Client-IP是他的weblogic插件加上的頭
  • HTTP_CLIENT_IP:出自TCP/IP應用協議裏面提到的概念定義的Header,某些代理使用這個Header填寫真實IP
  • HTTP_X_FORWARDED_FOR:新的HTTP協議中定義的標準x-forwarded-for,可是比較早出現的沒有這個Header

若是上面的Header都沒有,那麼就從RemoteAddress裏面去拿,這是最後的選擇。

/**
 * 獲取真實ip
 *
 * @param request       HttpServletRequest
 * @param acceptInnerIp 是否能夠返回內網ip
 * @return 真實ip
 */
public static String getRemoteIpByServletRequest(HttpServletRequest request, boolean acceptInnerIp) {
    String ip = request.getHeader("x-forwarded-for");
    if (StringUtils.isNotBlank(ip)) {
        // 屢次反向代理後會有多個ip值,第一個ip纔是真實ip
        if (ip.indexOf(",") != -1) {
            ip = ip.split(",")[0];
        }
    }
    if (isIpValid(ip)) {
        return ip;
    }
    ip = request.getHeader("Proxy-Client-IP");
    if (isIpValid(ip)) {
        return ip;
    }
    ip = request.getHeader("WL-Proxy-Client-IP");
    if (isIpValid(ip)) {
        return ip;
    }
    ip = request.getHeader("HTTP_CLIENT_IP");
    if (isIpValid(ip)) {
        return ip;
    }
    ip = request.getHeader("HTTP_X_FORWARDED_FOR");
    if (isIpValid(ip)) {
        return ip;
    }
    ip = request.getHeader("X-Real-IP");
    if (isIpValid(ip)) {
        return ip;
    }
    ip = request.getRemoteAddr();
    return ip;
}

/**
 * 判斷是否有效
 * @param ip ip
 * @param acceptInnerIp 是否接受內網ip
 * @return
 */
private static boolean isIpValid(String ip, boolean acceptInnerIp) {
    return acceptInnerIp ? isIpValid(ip) : isIpValidAndNotPrivate(ip);
}

/**
 * 僅僅判斷ip是否有效
 * @param ip
 * @return
 */
private static boolean isIpValid(String ip) {
    if (StringUtils.isBlank(ip)) {
        return false;
    }
    String[] split = ip.split("\\.");
    if (split.length != 4) {
        return false;
    }
    try {
        long first = Long.valueOf(split[0]);
        long second = Long.valueOf(split[1]);
        long third = Long.valueOf(split[2]);
        long fourth = Long.valueOf(split[3]);
        return first < 256 && first > 0
                && second < 256 && second >= 0
                && third < 256 && third >= 0
                && fourth < 256 && fourth >= 0;
    } catch (NumberFormatException e) {
        return false;
    }
}

/**
 * 判斷ip是否有效,而且不是內網ip
 * @param ip
 * @return
 */
private static boolean isIpValidAndNotPrivate(String ip) {
    if (StringUtils.isBlank(ip)) {
        return false;
    }
    String[] split = ip.split("\\.");
    try {
        long first = Long.valueOf(split[0]);
        long second = Long.valueOf(split[1]);
        long third = Long.valueOf(split[2]);
        long fourth = Long.valueOf(split[3]);
        if (first < 256 && first > 0
                && second < 256 && second >= 0
                && third < 256 && third >= 0
                && fourth < 256 && fourth >= 0) {
            if (first == 10) {
                return false;
            }
            if (first == 172 && (second >= 16 && second <= 31)) {
                return false;
            }
            if (first == 192 && second == 168) {
                return false;
            }
            return true;
        }
        return false;
    } catch (NumberFormatException e) {
        return false;
    }
}
相關文章
相關標籤/搜索