import javax.servlet.http.HttpServletRequest;
public final class ClientInfoUtil {
private ClientInfoUtil() {} // 工具類禁止實例化
/**
* 獲取客戶端真實 IP,兼容反向代理、負載均衡器
* 檢查順序:X-Forwarded-For → Proxy-Client-IP → WL-Proxy-Client-IP → RemoteAddr
*/
public static String getRealIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (isInvalid(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (isInvalid(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (isInvalid(ip)) {
ip = request.getRemoteAddr();
}
// 多級代理取第一個非 unknown IP
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; // IPv6 本地回環轉換
}
/**
* 獲取客戶端訪問的完整 URL(含協議、域名、路徑、查詢串)
*/
public static String getFullUrl(HttpServletRequest request) {
StringBuilder url = new StringBuilder();
url.append(request.getScheme()).append("://");
url.append(request.getHeader("Host")); // 含端口
url.append(request.getRequestURI());
String query = request.getQueryString();
if (query != null) {
url.append('?').append(query);
}
return url.toString();
}
private static boolean isInvalid(String ip) {
return ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip);
}
}
client, proxy1, proxy2,首個為真實客戶端 IPgetHeader 忽略大小寫0:0:0:0:0:0:0:1 ↔ 127.0.0.1 統一轉換:port@RestController
public class DemoController {
@GetMapping("/info")
public Map<String, String> info(HttpServletRequest request) {
return Map.of(
"clientIp", ClientInfoUtil.getRealIp(request),
"fullUrl", ClientInfoUtil.getFullUrl(request)
);
}
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass https://backend;
}
ClientInfoUtil 以 20 行核心代碼 解決了代理鏈路下真實 IP 識別與完整 URL 重構兩大高頻需求。生產環境建議搭配 Nginx 透傳 Header 並接入 分布式追蹤系統(如 SkyWalking、Pinpoint)實現全鏈路可觀測。