在 JSP/Servlet 中使用 request.getRemoteAddr() 可取得客戶端 IP,但經過 Apache、Squid、Nginx 等反向代理後,該方法只會返回代理伺服器 IP(如 127.0.0.1 或內網位址),而非真實客戶端 IP。
代理轉發請求時,會在 HTTP 標頭加入 X-Forwarded-For(簡稱 XFF)欄位,記錄原始客戶端 IP 與轉發鏈路。標準格式為:
X-Forwarded-For: client, proxy1, proxy2
最左側為最原始客戶端 IP,依序為經過的代理節點。
public String getRemoteIP(HttpServletRequest request) {
String xff = request.getHeader("x-forwarded-for");
return (xff == null) ? request.getRemoteAddr() : xff;
}
public String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
當經過多級反向代理,X-Forwarded-For 會包含一串 IP:
X-Forwarded-For: 192.168.1.110, 192.168.1.120, 192.168.1.130, 192.168.1.100
真實客戶端 IP 為第一個非 unknown 的有效 IP,即 192.168.1.110。
public String getRealIp(HttpServletRequest request) {
String xff = request.getHeader("X-Forwarded-For");
if (xff != null && !xff.isEmpty() && !"unknown".equalsIgnoreCase(xff)) {
// 取第一個有效 IP
String[] ips = xff.split(",");
for (String ip : ips) {
String trimmed = ip.trim();
if (!trimmed.isEmpty() && !"unknown".equalsIgnoreCase(trimmed)) {
return trimmed;
}
}
}
// 回退其他標頭
String[] headers = {"Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
for (String header : headers) {
String ip = request.getHeader(header);
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
}
return request.getRemoteAddr();
}
server.forward-headers-strategy=framework 或使用 Filter 統一處理獲取真實 IP 核心在於正確解析 X-Forwarded-For 鏈路,取首個有效非未知 IP,並建立多層回退機制。生產環境務必結合信任代理清單與 IP 驗證,防範標頭偽造攻擊。