首頁

目前文章總數:203 篇

  

最後更新:2025年 10月 25日

0103. ASP.NET Core Web 正確取得客戶端真實 IP:ForwardedHeaders 完整指南

日期:2025年 11月 15日

標籤: Asp.NET Core Asp.NET Core Web MVC C# Nginx Docker

摘要:C# 學習筆記


範例所需:1. Visual Studio 2022 以上版本
     2. Linux 主機(本篇用 Ubuntu 22.04)
     3. Linux 主機已安裝 Nginx
     4. Linux 主機已安裝 Docker
範例檔案:本篇範例代碼
解決問題:在 Asp.Net Web 中,如何更精確的補捉用戶端的真實IP,並實際部署 Nginx + Docker 的方式說明此設定的差異
基本介紹:本篇分為五大部分。
第一部分:前置作業 - 環境架構
第二部分:問題描述
第三部分:解決方案
第四部分:DEMO 成果
第五部分:生產環境實務探討






第一部分:前置作業 - 環境架構

Step 1:前置作業 - 環境架構

以下是此次說明用到的環境架構

角色   說明
1. 用戶端 訪問的用戶 IP : 192.168.242.76
2. 伺服器主機 提供用戶訪問的 Web 機器,機器 IP:192.168.51.93
3. Nginx (伺服器內) 安裝於伺服器主機,並且與容器 8888 Port 綁定,對外反向代理 8889
    反向代理後完整路徑: 192.168.51.93:8889
4. 容器 (伺服器內) 安裝於伺服器主機,Docker 容器,使用 8888 Port
    用戶直連完整路徑: 192.168.51.93:8888




Step 2:前置作業 - Web 開放的 API

並且運行的 Web 應用程式安裝在容器上,並且開放了以下端口:
※取得 IP 使用的屬性 : HttpContext.Connection.RemoteIpAddress

/// <summary>
/// 當前訪問者(用戶) 後端紀錄的 IP
/// </summary>        
[HttpGet]
public IActionResult CallAPI()
{
    var remoteIp = HttpContext.Connection.RemoteIpAddress;
    // 方便 Demo 說明轉換成純 IPv4
    var ipv4 = remoteIp?.MapToIPv4();
    var result = new
    {
        RemoteIp = ipv4.ToString(),
        Scheme = HttpContext.Request.Scheme,
        Host = HttpContext.Request.Host.Value
    };
    return Ok(result);
}


並且對應的呼叫 API 路由如下:

http://{Domain}//Home//CallAPI




第二部分:問題描述

Step 1:用戶端-直連 - 可以正常取得

我們透過本機電腦訪問容器位址,中間過程沒有任何 反向代理 ,視為直連




輸入以下 URL 網址列,可以看到 API 反饋的 IP 為 192.168.242.76 正確的獲得了訪問者的 IP

http://192.168.51.93:8888/Home/CallAPI




Step 2:用戶端-經過代理 - 問題出現

我們透過本機電腦訪問 Nginx 對外位址,有 經過反向代理




輸入以下 URL 網址列,可以看到 API 反饋的 IP 為 192.168.51.93 這是 伺服器IP 而非用戶IP

http://192.168.51.93:8889/Home/CallAPI




Step 3:問題描述 - 為何無法取得真實 IP

這實際上與轉發標頭 X-Forwarded-For 有關,以下是不同訪問模式下的轉發層級:

用戶訪問模式   經過路徑 層數(自己算1)
直連 用戶 => 容器 1
經過代理 用戶 => Nginx -> 容器 2


直連時,HttpContext.Connection.RemoteIpAddress 會顯示用戶的真實 IP
經過代理時,HttpContext.Connection.RemoteIpAddress 只能看到最近一層代理的 IP (Nginx IP),而不是用戶的真實 IP

第三部分:解決方案

Step 1:範例代碼架構

打開本篇範例代碼後,架構基本分成以下:
調整前專案 :NotUseForwardedHeadersExample
調整後專案 :UseForwardedHeadersExample

1. 初始化配置 初始化配置 ForwardedHeadersOptions ,使其可觀察到真實 IP
2. 控制器 取得 HttpContext 組態資料
3. Web 檢視器 響應 API 的結果到頁面上,便於觀察
4. Dockerfile 容器化部署會用到的 Dockerfile




Step 2:解決方案 - 設定初始化配置

在初始化配置中設定以下,添加 ForwardedHeaders,以下是解決此問題所需參數與說明:

1. ForwardedHeaders 處理 X-Forwarded-For (客戶端真實 IP)
  處理 X-Forwarded-Proto (原始協定 http/https)
2. ForwardLimit 限制最多處理幾層代理


using Microsoft.AspNetCore.HttpOverrides;
try
{
    var builder = WebApplication.CreateBuilder(args);

    // Add services to the container.
    builder.Services.AddControllersWithViews();

    var app = builder.Build();

    // 1. 添加 ForwardedHeaders 
    var forwardedHeadersOptions = new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
        ForwardLimit = 5
    };  
    app.UseForwardedHeaders(forwardedHeadersOptions);

    //... 略

    app.Run();

}
catch (Exception ex)
{
    Console.WriteLine(ex);
}


至此設定完成後,可以直接進到 第四部分:DEMO 成果 ,第三部份後半段為安全性、建議說明

Step 3:完整配置 ForwardedHeaders

以下是說明 ForwardedHeaders 的其他參數,基本上會用到的只有 3 種參數配置,其餘多半在生產環境上可以不用配置

參數 用途 生產環境是否必須 說明
1. ForwardedHeaders 指定要處理哪些轉發標頭(Header) - 依照需求設定
2. ForwardLimit 限制最多處理幾層代理 合理配置數值,設為 Null 會為不限制,有風險
3. KnownProxies 信任的代理 IP 清單 依照生產環境配置,信任已知的負載平衡、反向代理IP
4. KnownNetworks 信任的網路區段 依照生產環境配置網路區段,信任內網區段
5. AllowedHosts 允許的主機名 X HostFiltering 中介軟體專門處理主機名驗證,並免同時使用
6. RequireHeaderSymmetry 標頭對稱性檢查 X 預設 False ,若需要強制所有代理層標頭一致才需要
7. ForwardedForHeaderName 自訂標頭名稱 X 遵循標頭規範名稱,請勿自訂名稱,通常用來做為測試,或客製化使用
8. OriginalForHeaderName 用於保存原始標頭值 X 不用,中間的代理紀錄更為合適
9. OriginalProtoHeaderName 用於保存原始標頭值 X 不用,中間的代理紀錄更為合適


以下是預設代碼參考:

var forwardedHeadersOptions = new ForwardedHeadersOptions
{
    // 處理所有轉發標頭
    ForwardedHeaders = ForwardedHeaders.XForwardedFor 
                     | ForwardedHeaders.XForwardedProto 
                     | ForwardedHeaders.XForwardedHost,        
    // 合理的轉發                
    ForwardLimit = 5,    
};

// 信任特定的代理伺服器
forwardedHeadersOptions.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));

// 或信任整個內網
forwardedHeadersOptions.KnownNetworks.Add(
    new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)
);
forwardedHeadersOptions.KnownNetworks.Add(
    new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)
);
forwardedHeadersOptions.KnownNetworks.Add(
    new IPNetwork(IPAddress.Parse("192.168.0.0"), 16)
);





第四部分:DEMO 成果

Step 1:重新佈署

延續 第三部分 Step 2. 將調整後的代碼,重新部署代碼到環境


Step 2:用戶端-直連 - 可以正常取得

直連的方式


仍可以正常取得 IP - 符合預期


Step 2:用戶端-經過代理 - 可以正常取得

經過反向代理 的方式


也可以正常取得 IP - 在有反向代理的情況下,仍能更有機會取得真實的用戶IP,解決了無法取得用戶真實 IP 的問題
強調:始終不可以相信前端用戶傳給伺服器的任何東西,包含 IP,這是可以偽造的,但透過此方法是讓我們能更有高精確 + 安全性的取得用戶IP




第五部分:生產環境實務探討

Step 1:為何要用 ForwardedHeadersOptions

問題 : 在這邊點出個問題,有必要使用 ForwardedHeadersOptions 來取得真實 IP 嗎? 即使不用此方法,不也可以取得
解答 : 是的,但一般代碼可能實作如下(Web API):

[HttpGet]
public IActionResult GetClientIP()
{
    char[] Separator = { ',' };
    var httpRequest = HttpContext.Request;
    var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty;
    if (HttpContext.Request.Headers.Count == 0)
    {
        Console.WriteLine("沒有請求 Header 視為異常,不解析 IP");
        return BadRequest();
    }
    // 1. 檢查標頭 X-Forwarded-For - 自己實作
    if (Request.Headers.TryGetValue("X-Forwarded-For", out var forwardedIps))
    {
        var ips = forwardedIps.ToString().Split(Separator, StringSplitOptions.RemoveEmptyEntries);
        clientIp = ips[0];// 2. 直接取第 1 個 IP,缺少驗證任何來源,任何偽造都無方防護
    }

    // 3. 自己刻信任的網段、白名單 IP、黑名單 IP ... 略

    var result = new
    {
        RemoteIp = clientIp,                
    };
    return Ok(result);
}


開發團隊可以自己解析 Headers 內的東西,然後取得 IP,更完整實作的會檢查此 IP 是否符合公司內部信任域名 … 等
回到原本的問題:微軟已經有現成的 ForwardedHeadersOptions 並實作 KnownNetworks、KnownProxies 更安全的封裝方法,現代開發者應該避免自己花時間重刻方法。

Step 2:ForwardLimit 應該設定多少值

微軟給定的 ForwardLimit 預設值為 1 ,但實務上環境的架構很多種可能:

用戶 => CloudFlare => LoadBalnce => Nginx => 容器Nginx => 容器 
用戶 => Amazon Cloud => CloudFlare => LoadBalnce => Nginx => 容器
... 
...
...
太多種可能



以下是可以使用的常見架構下的參考值:

架構類型 架構圖示 ForwardLimit KnownNetworks 建議
本地開發 客戶端 → 應用 1 不需要
單層反向代理 客戶端 → Nginx → 應用 1-2 172.16.0.0/12, 192.168.0.0/16
Docker Compose 客戶端 → Nginx → Docker 容器 2 172.16.0.0/12
Kubernetes 基礎 客戶端 → Ingress → Service → Pod 2-3 10.0.0.0/8
雲端單層 客戶端 → AWS ALB → 應用 1 AWS VPC CIDR
CDN + 雲端 客戶端 → Cloudflare → ALB → 應用 2 VPC CIDR
完整企業架構 客戶端 → CDN → WAF → LB → Nginx → 應用 4-5 內網全段
Service Mesh 客戶端 → Ingress → Envoy → Envoy → Pod 3-5 Kubernetes Pod CIDR


因此多數的情況下,ForwardLimit 設定 5 已可解決常見的部署架構。


Step 3:生產環境面臨的問題 - 原則

X-Forwared-For 標頭是由客戶端提供給後端的,因此是可以輕易偽造,透過以下 Windows Batch 指令可以輕鬆偽造

curl -H "X-Forwarded-For: 8.8.8.8, 1.1.1.1" https://your-api.com/api/TestAPI


所以需保持一個原則 不可能 100% 取得客戶端真實IP

Step 4:生產環境面臨的問題 - 環境太多,配置複雜

雖然最終部署到生產環境,但開發的過程是循序漸進,不可能為每個環境都配置信任的域名、Limit

本機開發 → DEV → QAT → STG → PRO   


其次每個企業組織架構不同,當開發並 不能管理生產環境 時,往往面臨的將會是 開發團隊不清楚網路架構,因為是由另一個部門處理公司的對外網路與機器管理
因此 不要過度追求完美,符合實際部署需求,實用至上!