java nginx等代理或網關轉發請求后獲取客戶端的ip地址,原理


在沒有網關或者反向代理軟件情況下,java里獲取客戶端ip地址的方法是request.getRemoteAddr()
先解釋下http協議和TCP協議:
網頁默認是進行http連接了,http協議即超文本傳送協議(Hypertext Transfer Protocol ),是工作TCP協議之上的協議
tcp連接需要三次握手,也就是調用底層的socket進行連接確認。而socket連接需要知道通信雙方的ip地址和端口才可以進行數據的正確傳輸。
由上面可以知道request.getRemoteAddr()方法其實就是獲取的連接的客戶端socket的ip地址。
 
但如果我們客戶端將要發送接口請求先發送到一台代理請求服務器或者網關后,再由他們進行數據請求,
這時我們使用上面的getRemoteAddr()方法獲取到了ip地址就是代理請求服務器或網關的ip地址。
而不是真實的客戶端ip地址
也就是說通過nginx等反向代理軟件時,不能通過這個方法獲取客戶端真是ip地址。
 
如真實ip:223.104.1.240 廣東省廣州市 移動
訪問域名www.zwh.com
nginx所在服務器193.112.28.110
這個域名和nginx所在服務器綁定,
客戶端訪問時,如www.zwh.com/項目名/index.jsp
這個訪問了是nginx的80端口,然后nginx再將這個請求重定向到localhost:8080/項目名/index.jsp
而nginx在將請求進行重定向時會在請求頭header中增加一個x-forwarded-for信息,用以跟蹤原有客戶端ip地址和原來客戶端請求的服務器地址
存放的就是x-forwarded-for223.104.1.240
 
對應java方法來說,request.getRemoteAddr(),因為直接請求的客戶端是nginx,在同一服務器上,所以這個方法獲取到的ip地址是127.0.0.1或者193.112.28.110

 

 
要獲取真實ip地址就要使用request.getHeader("x-forwarded-for")獲取請求頭部真實ip地址
,但是當xxx.conf配置server節點時,forwarded_for設置為off,則X-Forwarded-For:unknown
默認forwarded_for是on
 
先看下nginx的部分配置
location / {
            proxy_redirect off;
            proxy_pass  http://localhost:8080;
            proxy_set_header  Host $host;
            proxy_set_header   X-real-ip $remote_addr;
            proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        }

繼續使用上面數據

這個$host 其實是nginx服務器本身的ip地址:193.112.28.110
 $remote_addr這個地址是想nginx請求的客戶端地址:223.104.1.240
 $proxy_add_x_forwarded_for 這個變量和remote_addr的區別是
proxy_add_x_forwarded_for會將經過的所有代理ip相加起來,
remote_addr則只是獲取到直接連接到nginx的ip地址
  在有多個代理服務器的情況下,會將代理服務器的X-Forwarded-For頭部的ip地址和客戶端的ip地址相加,
  即:X-Forwarded-For請求頭部header的ip字符串  + 直接向nginx服務器發送請求的客戶端地址
   換個表達請求的過程是client--->nginx1--->nginx2---->tomcat
  而假設client:ip1, nginx1:ip2, nginx2:ip3, tomcat:ip4
  那么請求request到tomcat時,
  請求頭部header中的X-Forwarded-For信息是=ip1,ip2,ip3

 

簡單了方法

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;
}

上面三個請求頭,大部分的代理或者網關都會加上x-forwarded-for:clientip,proxy1ip,proxy2ip

Proxy-Client-IP這個一般是apache,http服務器才會有,經過apache代理時,apache會在請求頭加上x-forwarded-for和Proxy-Client-IP

WL-Proxy-Client-IP這個一般是weblogic才會加上的頭部

 

注意:

 

1、這些請求頭都不是http協議里的標准請求頭,也就是說這個是各個代理服務器自己規定的表示客戶端地址的請求頭。如果哪天有一個代理服務器軟件用oooo-client-ip這個請求頭代表客戶端請求,那上面的代碼就不行了。

2、這些請求頭不是代理服務器一定會帶上的,網絡上的很多匿名代理就沒有這些請求頭,所以獲取到的客戶端ip不一定是真實的客戶端ip。代理服務器一般都可以自定義請求頭設置。

3、即使請求經過的代理都會按自己的規范附上代理請求頭,上面的代碼也不能確保獲得的一定是客戶端ip。不同的網絡架構,判斷請求頭的順序是不一樣的。

4、最重要的一點,請求頭都是可以偽造的。如果一些對客戶端校驗較嚴格的應用(比如投票)要獲取客戶端ip,應該直接使用ip = request.getRemoteAddr (),雖然獲取到的可能是代理的ip而不是客戶端的ip,但這個獲取到的ip基本上是不可能偽造的,也就杜絕了刷票的可能。(有分析說arp欺騙+syn有可能偽造此ip,如果真的可以,這是所有基於TCP協議都存在的漏洞),這個ip是tcp連接里的ip。
鏈接

 

ip工具類

import javax.servlet.http.HttpServletRequest;
/**
* IP地址工具類
* @author xudongdong
*
*/
public class IpUtil {
    
    /**
     * 私有化構造器
     */
    private IpUtil() {
    }
    /**
     * 獲取真實IP地址
     * <p>使用getRealIP代替該方法</p>
     * @param request req
     * @return ip
     */
    @Deprecated
    public static String getClinetIpByReq(HttpServletRequest request) {
        // 獲取客戶端ip地址
        String clientIp = request.getHeader("x-forwarded-for");
        if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getHeader("Proxy-Client-IP");
        }
        if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getHeader("WL-Proxy-Client-IP");
        }
        if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) {
            clientIp = request.getRemoteAddr();
        }
        /*
         * 對於獲取到多ip的情況下,找到公網ip.
         */
        String sIP = null;
        if (clientIp != null && !clientIp.contains("unknown") && clientIp.indexOf(",") > 0) {
            String[] ipsz = clientIp.split(",");
            for (String anIpsz : ipsz) {
                if (!isInnerIP(anIpsz.trim())) {
                    sIP = anIpsz.trim();
                    break;
                }
            }
            /*
             * 如果多ip都是內網ip,則取第一個ip.
             */
            if (null == sIP) {
                sIP = ipsz[0].trim();
            }
            clientIp = sIP;
        }
        if (clientIp != null && clientIp.contains("unknown")){
            clientIp =clientIp.replaceAll("unknown,", "");
            clientIp = clientIp.trim();
        }
        if ("".equals(clientIp) || null == clientIp){
            clientIp = "127.0.0.1";
        }
        return clientIp;
    }
    
    /**
     * 判斷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");
    }
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM