8 September 2026

Java 後端服務中正確取得客戶端真實 IP 位址的方法與常見錯誤排查

Java 後端服務中正確取得客戶端真實 IP 位址的方法與常見錯誤排查

你的 Java 後端日誌裡,客戶端 IP 永遠顯示同一個地址。仔細一看,那個地址根本不是終端使用者的,而是某台 Nginx 機器的內網 IP。這種狀況幾乎出現在所有部署於反向代理或負載均衡器後面的服務裡。HttpServletRequest.getRemoteAddr() 只告訴你「最後一個跟你建立 TCP 連線的機器是誰」,而不是請求真正的源頭。這篇文章要把這件事說清楚,並給你可以直接用在生產環境的解法。

開發者速覽

在多層代理架構下,Java 的 getRemoteAddr() 只會回傳代理伺服器的 IP,而非真正的客戶端位址。正確做法是優先讀取 X-Forwarded-For 等 HTTP 標頭的第一個值,但這些標頭可以被任意偽造,因此必須搭配可信代理層的設定來防護。部署新環境時,用外部 IP 工具做驗證是確認解析邏輯正確運作最快的方式。

為何 getRemoteAddr() 在代理環境下會失效

當客戶端請求直接打到你的 Java 應用,getRemoteAddr() 完全沒有問題,它回傳的就是那條 TCP 連線的來源位址。

但現實的生產環境幾乎不會這樣架設。通常你的應用前面至少有一層 Nginx 作為反向代理,更複雜的架構可能還有 CDN、WAF 或企業防火牆。這時每一個節點都把自己的地址當成「連線來源」傳給下一層。你的 Servlet 最終只看到離它最近的那個代理伺服器的 IP。

這不是 Java 的問題,也不是 Servlet 容器的缺陷。這是 TCP 協定的本質。每一跳(hop)都建立一條全新的連線,前一跳的來源地址不會自動往後傳遞。

所以你需要的資訊,不在 TCP 層,而在 HTTP 標頭裡。

X-Forwarded-For 標頭的運作邏輯

為了讓後端服務能夠識別原始客戶端,代理伺服器在轉發請求時會在 HTTP 標頭裡附上 IP 資訊。格式大致如下:

X-Forwarded-For: 203.0.113.45, 10.0.0.1, 192.168.1.2

每一跳的代理都會把它所看到的上游 IP 附加到列表末尾。所以真正的客戶端 IP 通常排在最左邊,也就是第一個值。IETF 在 2012 年正式定義了這個概念的標準版本,記錄在 Forwarded 標頭規範 之中,提出以標準化的 Forwarded 標頭取代各廠商自訂的 XFF,不過實務上 XFF 至今仍是最普遍的格式。

三種常見 IP 傳遞標頭的特性對照

標頭名稱 格式 支援程度 備註
X-Forwarded-For 逗號分隔列表,最左為原始客戶端 幾乎所有代理/CDN 最常見,但易被偽造
X-Real-IP 單一 IP 值 Nginx 專屬設定 需手動在 Nginx 中設定
Forwarded 結構化鍵值格式 IETF 標準,支援仍不普及 語法嚴謹,解析較複雜

在 Java 中正確解析真實 IP 的實作方式

以下這段工具方法是很多 Java 後端服務的標準做法,邏輯清晰,可以直接採用或依需求調整:

public static String getRealIp(HttpServletRequest request) {
    String xff = request.getHeader("X-Forwarded-For");
    if (xff != null && !xff.isBlank()) {
        for (String part : xff.split(",")) {
            String candidate = part.trim();
            if (!candidate.isEmpty()) {
                return candidate;
            }
        }
    }

    String xRealIp = request.getHeader("X-Real-IP");
    if (xRealIp != null && !xRealIp.isBlank()) {
        return xRealIp.trim();
    }

    return request.getRemoteAddr();
}

這段程式碼的邏輯是:優先讀取 X-Forwarded-For 第一個非空白值,次選 X-Real-IP,最後才 fallback 到 getRemoteAddr()。無論是哪種代理架構,這個順序基本上都能正確取得客戶端 IP。

要注意的是,split(",") 後面的每個值可能含有前置空格,所以 trim() 是必要的,否則你取到的 IP 字串裡可能夾著空白字元,後續比對或儲存時會出問題。

X-Forwarded-For 偽造攻擊與防範策略

這裡有個讓很多人踩坑的細節:X-Forwarded-For 只是一個普通的 HTTP 請求標頭,攻擊者可以在發送請求時自行加上這個標頭,填入任意值。如果你的伺服器直接信任這個標頭,攻擊者就能讓自己看起來像是來自任何 IP 位址,包括受白名單保護的地址。

以下幾個措施能有效降低偽造風險:

  • 在 Nginx 設定中使用 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;,並確保在請求進入你的網路邊界時清除或覆蓋外部傳入的 XFF 標頭。
  • 如果你用的是 Spring Boot,設定 server.forward-headers-strategy=FRAMEWORK 並搭配 server.tomcat.remoteip.internal-proxies 指定可信代理的 IP 段,框架會自動過濾不可信來源的標頭。
  • 存取控制、速率限制等安全敏感的邏輯,不應單獨依賴客戶端宣稱的 IP,必須同時驗證請求路徑是否經過你受控的代理層。

核心原則是:只相信你控制的代理層所附加的 IP,任何可以直接從外部設定的標頭值,都不應該被無條件信任。

如何驗證你的服務實際對外暴露的 IP

開發完解析邏輯後,還有一件事值得養成習慣:在部署新環境或進行 staging 驗證時,確認你的服務或客戶端對外呈現的實際 IP 是否符合預期。

最快的做法是從伺服器端發出一個 HTTP 請求,看看對外公開的出口 IP 是哪個。你可以透過 IP lookup 查詢特定 IP 的詳細屬性,包括地理位置、所屬 ASN、是否屬於已知的代理或資料中心節點。這在 debug 過程中非常實用,能幫你快速判斷「你解析到的這個 IP 合理嗎」,而不是在日誌裡憑感覺推測。

如果你在自動化測試流程中需要驗證出口 IP,在 CI 環境的 setup 階段執行一次外部 IP 檢查,可以提早發現代理設定錯誤,避免問題到了生產環境才被發現。

常見錯誤場景與對應的排查思路

實務上開發者遇到的 IP 解析問題,通常集中在幾個固定場景。了解這些場景能讓你更快定位根因,而不是盲目改程式碼。

  • IP 永遠是 127.0.0.1 或內網地址:幾乎一定是代理未正確設定 XFF 標頭。檢查 Nginx 設定,確認 proxy_set_header 指令存在且指向正確的變數。
  • XFF 標頭存在,但取到的是代理 IP 而非客戶端 IP:解析邏輯取錯了位置。客戶端 IP 在列表最左邊,不是最右邊,且要記得 trim() 去除空白。
  • IP 看起來合理,但地區和預期不符:客戶端本身可能走了 VPN 或代理。用 IP lookup 工具查詢該 IP 的 ASN 屬性,能快速確認這個假設。
  • 本機測試正常,部署後 IP 解析就出錯:本機缺少代理層,生產環境有多層代理。需在 staging 模擬完整的代理鏈,或在本機手動加上 XFF 標頭來重現場景。

框架層級的解決方案值得優先考慮

自己寫工具方法是可行的,但如果你用的是 Spring Boot,框架內建的支援更值得優先採用。

application.properties 加入以下設定:

server.forward-headers-strategy=FRAMEWORK
server.tomcat.remoteip.internal-proxies=10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}

啟用後,Spring 的 RemoteIpFilter 會自動處理 XFF 解析,並且讓 request.getRemoteAddr() 直接回傳正確的客戶端 IP。這樣你就不需要在每個 Controller 或 Filter 裡手動解析標頭,整個應用的 IP 取得邏輯集中在一個地方,維護起來也清晰得多。

Tomcat 之外,Undertow 和 Jetty 也有對應的 ProxyPeerAddressCustomizer 或類似機制。Jakarta EE 環境下,邏輯相同,只是設定方式依容器而異。重點始終是:讓代理 IP 解析集中在一個統一的層處理,而不是散落在應用程式各處。

IP 取對了,後端所有依賴它的判斷才有意義

客戶端 IP 在後端系統裡從來不只是一個日誌欄位。速率限制、地理封鎖、存取稽核、異常行為偵測,這些功能全部都建立在它的基礎上。如果一開始取得的 IP 就是錯的,後續所有依賴它的功能都會靜默失效,而且往往很難被發現,因為程式碼本身不會報錯。

正確取得客戶端 IP 的關鍵有三個層面:理解你的代理架構有幾層、選擇對應的正確標頭來源,以及防範偽造攻擊。這三件事都做到之後,最後一步是在每次部署後做一次外部驗證,確認環境設定和預期的 IP 解析行為一致。這個習慣成本很低,但能幫你省下在生產環境除錯時大量的時間和精力。

Related Post