問題再現
由於 tomcat 被 Nginx 反向代理, 因此request.getRemoteAddr()
只能獲取到本地回環地址,
Nginx 提供了變量 $remote_addr
, 可用於使用 proxy_set_header 設置頭信息, 實現向上游的 tomcat 服務器提供遠端客戶端IP地址
proxy_set_header X-Real-IP $remote_addr;
但是, 一旦使用了 CDN 的話, 這個地址就變成了 CDN 節點的地址, 並且同一個用戶不同時間發出的IP都不一樣.
CDN 通常會在 Header 中提供客戶端真實IP, 如 CloudFlare 在 x-forwarded-for
, cf-connecting-ip
中設置了此數據.
Nginx 提供了變量 $http_x_forwarded_for
用於獲取 x-forwarded-for 的值, 因此可以在 Nginx 配置文件中為 X-Real-IP 設置以下兩個值:
remote_addr
http_x_forwarded_for
它們分別代表了未使用 CDN 和使用了 CDN 的客戶端真實IP.
# Nginx 配置實現
#CloudFlare
set $CDN "CloudFlare";
#set $CDN "NO"; # 如果未使用 CloudFlare, 則取消該行代碼的注釋
set $Real_IP $remote_addr;
if ($CDN = "CloudFlare") {
set $Real_IP $http_x_forwarded_for;
}
#設置代理 Header
proxy_set_header Host $host;
proxy_set_header X-Real-IP $Real_IP;
......
# 民間傳說, 一個電信反代的 CF IP
104.25.183.205
這個IP的電信延遲非常低, 而且速度非常不錯, 你懂的!
當自選該IP后, x-real-ip 也就是 x-forwarded-for 變成了 59.174.137.107,140.206.61.106
cf-connecting-ip 更是變成了被反代的 140.206.61.106
這種特殊情況應當被考慮.
所以我寫了一個 static 方法, 用來從 HttpServlet 中獲取 Real-IP:
package common;
import javax.servlet.http.HttpServletRequest;
/**
* 系統工具 & 配置
*/
public class Sys {
private static final CDN cdn;
private enum CDN {
Nginx, CloudFlare,
}
static {
cdn = CDN.Nginx;
}
public static String getRemoteIP(HttpServletRequest thiz) {
switch (cdn) {
case Nginx:
return thiz.getHeader("X-Real-IP");
case CloudFlare:
return thiz.getHeader("CF-Connecting-IP");
default:
return thiz.getRemoteAddr();
}
}
}