阿里雲CDN + nginx多級代理獲取客戶端IP

F嘉陽發表於2019-04-03

無Nginx代理場景

業務層通過獲取請求頭引數即可拿到客戶端IP

request.getRemoteAddr();
複製程式碼

一級Nginx代理

使用代理後直接讀取請求頭引數會讀取到代理伺服器的IP地址,而非真實客戶端IP

解決方法是新增Nginx請求頭引數提前儲存客戶端IP

nginx.conf配置加入內容

location / {
    ...
    # IP地址轉發
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Real-Port $remote_port;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
複製程式碼

業務層讀取nginx配置的請求頭引數即可

public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Real-IP");
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            log.info("【Proxy-Client-IP】 {}", ip);
        }
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            log.info("【WL-Proxy-Client-IP】{}", ip);
        }
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
            log.info("【X-Forwarded-For】{}", ip);
        }
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            log.info("【unknown】{}", ip);
        }
        return ip;
    }
複製程式碼

多級Nginx代理

若存在多級Nginx代理,則需要在第一級代理時獲取客戶端IP,在後續代理逐層傳遞

第一級代理配置同上,第N級代理配置nginx.conf如下

location /{
   # IP地址轉發
   proxy_set_header X-Real-IP $X-Real-IP;
   proxy_set_header X-Real-Port $X-Real-Port;
   proxy_set_header X-Forwarded-For $X-Forwarded-For;
}
複製程式碼

業務層保持不變即可讀取到傳遞的IP地址

阿里雲CDN轉發

在已經存在nginx代理的場景下,加入CDN後源客戶端IP在CDN處被轉發,故第一級nginx代理使用$remote_addr引數讀取到的是CDN服務的地址,而根據一般約定,CDN轉發會將IP地址存放在X-Forwarded-For引數下

修改頂級Nginx配置以支援優先獲取CDN代理地址,也可以通過修改業務層讀取優先順序實現

location / {
    ...
    # IP地址轉發
    # proxy_set_header X-Real-IP $remote_addr;
    # proxy_set_header X-Real-Port $remote_port;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
複製程式碼

但經過對阿里雲CDN測試,發現X-Forwarded-For下不僅包含真實客戶端IP也包含CDN服務IP,故業務層需要做一定的處理進行區分

如圖,第一個為真實客戶端IP,第二個為CDN代理IP

1554256476626.png

public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Real-IP");
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            log.info("【Proxy-Client-IP】 {}", ip);
        }
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            log.info("【WL-Proxy-Client-IP】{}", ip);
        }
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
            if (ip.contains(",")) {
                // 通過阿里雲CDN轉發後可能讀取到2個IP地址
                String[] cdnMutilIp = ip.split(",");
                ip = cdnMutilIp[0];
            }
            log.info("【X-Forwarded-For】{}", ip);
        }
        if (ip == null || ip.length() == 0 || " unknown ".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            log.info("【unknown】{}", ip);
        }
        return ip;
    }
}
複製程式碼

相關文章