設定 ASP.NET Core 以處理 Proxy 伺服器和負載平衡器

作者:Chris Ross

在建議的 ASP.NET Core 組態中,會使用適用於 IIS 的 ASP.NET Core 模組 (ANCM)、Nginx 或 Apache 來裝載應用程式。 Proxy 伺服器、負載平衡器及其他網路設備通常會在要求觸達應用程式之前,遮蔽要求的相關資訊:

  • 透過 HTTP 作為 Proxy 來處理 HTTPS 要求時,原始配置 (HTTPS) 會遺失而必須在標頭中轉送。
  • 由於應用程式會從 Proxy 而不是從網際網路或公司網路上的真實來源收到要求,因此必須也在標頭中轉送原始用戶端 IP 位址。

此資訊在要求處理方面可能相當重要,例如在重新導向、驗證、連結產生、原則評估及用戶端地理位置方面。

對於要在 Web 伺服器陣列上執行的應用程式,應參閱 Web 伺服器陣列中的主機 ASP.NET Core

轉送的標頭

依照慣例,Proxy 會以 HTTP 標頭轉送資訊。

標頭 描述
X-Forwarded-For (XFF) 針對在 Proxy 鏈結中起始要求及後續 Proxy 的用戶端,保存用戶端的相關資訊。 此參數可能包含 IP 位址,以及視需要可能會有連接埠號碼。 在 Proxy 伺服器鏈結中,第一個參數會指出起始要求的用戶端。 後面接著後續的 Proxy 識別碼。 鏈結中的最後一個 Proxy 並不在參數清單中。 最後一個 Proxy 的 IP 位址 (以及視需要會有連接埠號碼) 會在傳輸層以遠端 IP 位址的形式提供。
X-Forwarded-Proto (XFP) 原始配置的值 (HTTP 或 HTTPS)。 如果要求周遊了多個 Proxy,則此值也可能是一個配置清單。
X-Forwarded-Host (XFH) 主機標頭欄位的原始值。 通常,Proxy 不會修改主機標頭。 如需有關權限提高弱點的資訊,請參閱 Microsoft 資訊安全諮詢 CVE-2018-0787 \(英文\),此弱點會影響 Proxy 不會驗證或限制主機標頭為已知有效值的系統。

轉送標頭中介軟體ForwardedHeadersMiddleware 會讀取這些標頭,並將其填入 HttpContext 上的相關聯欄位。

中介軟體會更新:

如需上述內容的詳細資訊,請參閱此 GitHub 問題

您可以設定「轉送的標頭中介軟體」的預設設定。 對於預設設定:

  • 在應用程式與要求的來源之間只有「一個 Proxy」
  • 針對已知的 Proxy 和已知的網路,只會設定回送位址。
  • 轉送標頭名稱為 X-Forwarded-ForX-Forwarded-Proto
  • ForwardedHeaders 值為 ForwardedHeaders.None,必須在這裡設定所需的轉寄站,才能啟用中介軟體。

並非所有網路設備在新增 X-Forwarded-ForX-Forwarded-Proto 標頭時都不含額外組態。 如果透過 Proxy 傳送的要求在觸達應用程式時未包含這些標頭,請向您的設備製造商尋求指引。 如果設備使用的名稱不是 X-Forwarded-ForX-Forwarded-Proto,請設定 ForwardedForHeaderNameForwardedProtoHeaderName 選項,以符合設備使用的標頭名稱。 如需詳細資訊,請參閱轉送標頭中介軟體選項使用不同標頭名稱之 Proxy 的組態

IIS/IIS Express 和 ASP.NET Core 模組

當應用程式是在 IIS 和 適用於 IIS 的 ASP.NET Core 模組 (ANCM) 的後方進行跨處理序裝載時,IIS 整合中介軟體預設會啟用「轉送標頭中介軟體」。 「轉送標頭中介軟體」在啟用後會先在中介軟體管線中搭配 ASP.NET Core 模組限定的組態來執行。 限定的組態是基於有與轉送標頭相關的信任考量,例如 IP 詐騙。 中介軟體會經設定來轉送 X-Forwarded-ForX-Forwarded-Proto 標頭,並限制成單一 localhost Proxy。 如果需要額外的設定,請參閱轉送的標頭中介軟體選項

其他 Proxy 伺服器和負載平衡器案例

除了在跨處理序裝載時使用 IIS 整合之外,都未預設啟用轉送標頭中介軟體。 必須啟用「轉送的標頭中介軟體」,應用程式才能使用 UseForwardedHeaders 來處理轉送的標頭。 啟用此中介軟體之後,如果未將任何 ForwardedHeadersOptions 指定給中介軟體,則預設的 ForwardedHeadersOptions.ForwardedHeaders 會是 ForwardedHeaders.None

使用 ForwardedHeadersOptions 設定中介軟體,以轉送 X-Forwarded-ForX-Forwarded-Proto 標頭。

轉送的標頭中介軟體順序

應在其他中介軟體之前執行轉送標頭中介軟體。 這種排序可確保依賴轉送標頭資訊的中介軟體可以耗用用於處理的標頭值。 轉送標頭中介軟體可以在診斷和錯誤處理之後執行,但必須在呼叫 UseHsts 之前執行:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseForwardedHeaders();
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseForwardedHeaders();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

或者,在診斷之前呼叫 UseForwardedHeaders

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

注意

若未指定 ForwardedHeadersOptions,或未使用 UseForwardedHeaders 直接套用到擴充方法,則要轉送的預設標頭是 ForwardedHeaders.None。 必須使用要轉送的標頭設定 ForwardedHeaders 屬性。

Nginx 組態

若要轉送 X-Forwarded-ForX-Forwarded-Proto 標頭,請參閱使用 Nginx 在 Linux 上裝載 ASP.NET Core。 如需詳細資訊,請參閱 NGINX:使用轉送的標頭

Apache 組態

X-Forwarded-For 會自動新增。 如需詳細資訊,請參閱 Apache 模組 mod_proxy:反向 Proxy 要求標頭。 如需如何轉送 X-Forwarded-Proto 標頭的資訊,請參閱使用 Apache 在 Linux 上裝載 ASP.NET Core

轉送的標頭中介軟體選項

ForwardedHeadersOptions 會控制轉送標頭中介軟體的行為。 下列範例會變更預設值:

  • 將轉送標頭中的項目數限制為 2
  • 新增已知的 Proxy 位址 127.0.10.1
  • 將轉送標頭名稱從預設的 X-Forwarded-For 變更為 X-Forwarded-For-My-Custom-Header-Name
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
選項 描述
AllowedHosts 依據 X-Forwarded-Host 標頭將主機限制成所提供的值。
  • 比較值時,會使用序數忽略大小寫的方式來比較。
  • 必須排除連接埠號碼。
  • 如果清單空白,即表示允許所有主機。
  • 最上層的萬用字元 * 代表會允許所有非空白的主機。
  • 允許使用子網域萬用字元,但不會比對出根網域。 例如,*.contoso.com 會比對出子網域 foo.contoso.com,但不會比對出根網域 contoso.com
  • 允許使用 Unicode 主機名稱,但會轉換成 Punycode 來進行比對。
  • IPv6 addresses 必須包含週框方括號,並採用慣例格式 (例如 [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789])。 IPv6 位址並未特別設計成會檢查不同格式間是否具有邏輯相等性,因此不會執行標準化。
  • 如果無法限制可允許的主機,可能會讓攻擊者偽造服務所產生的連結。
預設值是空的 IList<string>
ForwardedForHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedForHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-For 標頭,而使用其他標頭轉送資訊時,會使用此選項。

預設值為 X-Forwarded-For
ForwardedHeaders 識別應該處理哪個轉送子。 如需適用的欄位清單,請參閱 ForwardedHeaders 列舉。 指派給此屬性的一般值為 ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto

預設值為 ForwardedHeaders.None
ForwardedHostHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedHostHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-Host 標頭,而使用其他標頭轉送資訊時,會使用此選項。

預設值為 X-Forwarded-Host
ForwardedProtoHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedProtoHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-Proto 標頭,而使用其他標頭轉送資訊時,會使用此選項。

預設值為 X-Forwarded-Proto
ForwardLimit 限制所處理標頭中的項目數。 設定為 null 可停用限制,但應該只有在已設定 KnownProxiesKnownNetworks 的情況下,才這樣做。 設置非 null 值是一種預防措施 (但不是保證),以防止設定不正確的 Proxy 和來自網路上的旁路惡意要求。

「轉送的標頭中介軟體」會以相反順序 (從右至左) 處理標頭。 如果使用預設值 (1),除非 ForwardLimit 的值增加,否則只會處理標頭中最右邊的值。

預設值為 1
KnownNetworks 可從中接受轉送標頭的已知網路位址範圍。 請使用無類別網域間路由 (CIDR) 標記法來提供 IP 範圍。

若伺服器使用雙模式通訊端,會以 IPv6 格式 (例如,IPv4 中的 10.0.0.1 在 IPv6 中以 ::ffff:10.0.0.1 表示) 提供 IPv4 位址。 請參閱 IPAddress.MapToIPv6。 透過查看 HttpContext.Connection.RemoteIpAddress 以判斷是否需要此格式。

預設值為包含單一 IPAddress.Loopback 項目的 IList<IPNetwork>。
KnownProxies 可從中接受轉送標頭的已知 Proxy 位址。 請使用 KnownProxies 來指定確切的相符 IP 位址。

若伺服器使用雙模式通訊端,會以 IPv6 格式 (例如,IPv4 中的 10.0.0.1 在 IPv6 中以 ::ffff:10.0.0.1 表示) 提供 IPv4 位址。 請參閱 IPAddress.MapToIPv6。 透過查看 HttpContext.Connection.RemoteIpAddress 以判斷是否需要此格式。

預設值為包含單一 IPAddress.IPv6Loopback 項目的 IList<IPAddress>。
OriginalForHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XOriginalForHeaderName 所指定的標頭。

預設值為 X-Original-For
OriginalHostHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XOriginalHostHeaderName 所指定的標頭。

預設值為 X-Original-Host
OriginalProtoHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XOriginalProtoHeaderName 所指定的標頭。

預設值為 X-Original-Proto
RequireHeaderSymmetry 要求所處理 ForwardedHeadersOptions.ForwardedHeaders 的標頭值數目必須同步。

ASP.NET Core 1.x 中的預設值為 true。 ASP.NET Core 2.0 或更新版本中的預設值為 false

情節和使用案例

當可以新增轉送的標頭且所有要求都安全時

在某些情況下,可能無法將轉送的標頭新增至透過 Proxy 傳送給應用程式的要求。 如果 Proxy 強制要求所有公用外部要求都必須是 HTTPS,您可以在使用任何類型的中介軟體之前,先手動設定該配置:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next(context);
});

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

在開發或預備環境中,可以使用環境變數或其他組態設定來停用此程式碼:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

if (!app.Environment.IsProduction())
{
    app.Use((context, next) =>
    {
        context.Request.Scheme = "https";
        return next(context);
    });
}

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

處理變更要求路徑的路徑基底和 Proxy

有些 Proxy 會原封不動傳送路徑,但其中含有應移除才能正確路由傳送的應用程式基底路徑。 UsePathBaseExtensions.UsePathBase 中介軟體會將該路徑分割成 HttpRequest.Path,以及將應用程式基底路徑分割成 HttpRequest.PathBase

如果 /foo 是以 /foo/api/1 形式傳送之 Proxy 路徑的應用程式基底路徑,中介軟體就會使用下列命令,將 Request.PathBase 設定為 /foo,以及將 Request.Path 設定為 /api/1

app.UsePathBase("/foo");
// ...
app.UseRouting();

注意

使用 WebApplication (請參閱從 ASP.NET Core 5.0 遷移至 6.0) 時,必須在 UsePathBase 之後呼叫 app.UseRouting,路由中介軟體才能在比對路由之前觀察修改的路徑。 否則,會在 UsePathBase 重寫路徑之前比對路由,如中介軟體排序路由文章中所述。

反向再次呼叫應用程式時,則會重新套用原始路徑和路徑基底。 如需中介軟體順序處理的詳細資訊,請參閱 ASP.NET Core 中介軟體

如果 Proxy 會修剪路徑 (例如,將 /foo/api/1 轉送給 /api/1),請設定要求的 PathBase 屬性來修正重新導向和連結:

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next(context);
});

如果 Proxy 會新增路徑資料,請使用 StartsWithSegments 並指派給 Path 屬性,以捨棄部分路徑來修正重新導向和連結:

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next(context);
});

使用不同標頭名稱之 Proxy 的組態

如果 Proxy 未使用名為 X-Forwarded-ForX-Forwarded-Proto 的標頭轉送 Proxy 位址/連接埠與原始配置資訊,請設定 ForwardedForHeaderNameForwardedProtoHeaderName 選項,以符合 Proxy 使用的標頭名稱:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "HeaderNamUsedByProxy_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "HeaderNamUsedByProxy_X-Forwarded-Proto_Header";
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

轉送 Linux 和非 IIS 反向 Proxy 的配置

如果部署至 Azure Linux App Service、Azure Linux 虛擬機器 (VM),或 IIS 之外的任何其他反向 Proxy 後端,則呼叫 UseHttpsRedirectionUseHsts 的這些應用程式會使站台進入無限迴圈。 反向 Proxy 會終止 TLS,且正確的要求配置不知道有 Kestrel。 OAuth 和 OIDC 在此組態中也失敗,因為它們會產生不正確的重新導向。 UseIISIntegration 在 IIS 後端執行時,會新增並設定轉送標頭中介軟體,但沒有相符的 Linux (Apache 或 Nginx 整合) 自動組態。

若要從非 IIS 案例中的 Proxy 轉送配置,請將 ASPNETCORE_FORWARDEDHEADERS_ENABLED 設定為 true,以啟用轉送標頭中介軟體。 警告:此旗標使用專為雲端環境設計的設定,而且不會啟用 KnownProxies option 之類的功能來限制接受哪些 IP 轉寄站。

憑證轉送

Azure

若要設定 Azure App Service 進行憑證轉送,請參閱 為 Azure App Service 設定 TLS 相互驗證。 下列指引與設定 ASP.NET Core 應用程式有關。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
    options.CertificateHeader = "X-ARR-ClientCert");

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

其他 Web Proxy

如果使用的 Proxy 不是 IIS 或 Azure App Service 的應用程式要求路由 (ARR),請設定該 Proxy 以轉送其在 HTTP 標頭中收到的憑證。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
    options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

如果 Proxy 不是使用 base64 編碼憑證 (和 Nginx 一樣),則請設定 HeaderConverter 選項。 請考慮下列範例:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
    options.HeaderConverter = (headerValue) =>
    {
        // Conversion logic to create an X509Certificate2.
        var clientCertificate = ConversionLogic.CreateAnX509Certificate2();
        return clientCertificate;
    };
});

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

疑難排解

當標頭未如預期般轉送時,請啟用 debug 層級記錄和 HTTP 要求記錄。 UseHttpLogging 必須在 UseForwardedHeaders 之後呼叫:

using Microsoft.AspNetCore.HttpLogging;
using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddHttpLogging(options =>
{
    options.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders;
});

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

app.UseForwardedHeaders();
app.UseHttpLogging();

app.Use(async (context, next) =>
{
    // Connection: RemoteIp
    app.Logger.LogInformation("Request RemoteIp: {RemoteIpAddress}",
        context.Connection.RemoteIpAddress);

    await next(context);
});

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

如果指定的標頭中有多個值,「轉送的標頭中介軟體」會以相反順序 (從右至左) 處理標頭。 預設的 ForwardLimit1 (一),因此除非 ForwardLimit 的值增加,否則只會處理標頭中最右邊的值。

要求的原始遠端 IP 必須符合 KnownProxiesKnownNetworks 清單中的項目,系統才會處理轉送的標頭。 這可藉由不接受來自不受信任 Proxy 的轉送子,以限制標頭詐騙行為。 當偵測到未知的 Proxy 時,記錄會指出 Proxy 的位址:

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

在上述範例中,10.0.0.100 是 Proxy 伺服器。 如果伺服器是信任的 Proxy,請將伺服器的 IP 位址新增至 KnownProxies,或將信任的網路新增至 KnownNetworks。 如需詳細資訊,請參閱轉送的標頭中介軟體選項一節。

using Microsoft.AspNetCore.HttpOverrides;
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseForwardedHeaders();
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseForwardedHeaders();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

若要顯示記錄,請將 "Microsoft.AspNetCore.HttpLogging": "Information" 新增至 appsettings.Development.json 檔案:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.HttpLogging": "Information"
    }
  }
}

重要

只允許信任的 Proxy 以及網路轉送標頭。 否則,IP 詐騙攻擊有可能發生。

其他資源

在建議的 ASP.NET Core 設定中,是使用 IIS/ASP.NET Core 模組、Nginx 或 Apache 來裝載應用程式。 Proxy 伺服器、負載平衡器及其他網路設備通常會在要求觸達應用程式之前,遮蔽要求的相關資訊:

  • 透過 HTTP 作為 Proxy 來處理 HTTPS 要求時,原始配置 (HTTPS) 會遺失而必須在標頭中轉送。
  • 由於應用程式會從 Proxy 而不是從網際網路或公司網路上的真實來源收到要求,因此必須也在標頭中轉送原始用戶端 IP 位址。

此資訊在要求處理方面可能相當重要,例如在重新導向、驗證、連結產生、原則評估及用戶端地理位置方面。

轉送的標頭

依照慣例,Proxy 會以 HTTP 標頭轉送資訊。

標頭 描述
X-Forwarded-For 針對在 Proxy 鏈結中起始要求及後續 Proxy 的用戶端,保存用戶端的相關資訊。 此參數可能包含 IP 位址 (以及視需要可能會有連接埠號碼)。 在 Proxy 伺服器鏈結中,第一個參數會指出起始要求的用戶端。 後面接著後續的 Proxy 識別碼。 鏈結中的最後一個 Proxy 並不在參數清單中。 最後一個 Proxy 的 IP 位址 (以及視需要會有連接埠號碼) 會在傳輸層以遠端 IP 位址的形式提供。
X-Forwarded-Proto 原始配置的值 (HTTP/HTTPS)。 如果要求周遊了多個 Proxy,則此值也可能是一個配置清單。
X-Forwarded-Host 主機標頭欄位的原始值。 通常,Proxy 不會修改主機標頭。 如需有關權限提高弱點的資訊,請參閱 Microsoft 資訊安全諮詢 CVE-2018-0787 \(英文\),此弱點會影響 Proxy 不會驗證或限制主機標頭為已知有效值的系統。

轉送標頭中介軟體 (ForwardedHeadersMiddleware) 會讀取這些標頭,並將其填入 HttpContext 上的相關聯欄位。

中介軟體會更新:

如需上述內容的詳細資訊,請參閱此 GitHub 問題

您可以設定「轉送的標頭中介軟體」的預設設定。 對於預設設定:

  • 在應用程式與要求的來源之間只有「一個 Proxy」
  • 針對已知的 Proxy 和已知的網路,只會設定回送位址。
  • 轉送標頭名稱為 X-Forwarded-ForX-Forwarded-Proto
  • ForwardedHeaders 值為 ForwardedHeaders.None,必須在這裡設定所需的轉寄站,才能啟用中介軟體。

並非所有網路設備在新增 X-Forwarded-ForX-Forwarded-Proto 標頭時都不含額外組態。 如果透過 Proxy 傳送的要求在觸達應用程式時未包含這些標頭,請向您的設備製造商尋求指引。 如果設備使用的名稱不是 X-Forwarded-ForX-Forwarded-Proto,請設定 ForwardedForHeaderNameForwardedProtoHeaderName 選項,以符合設備使用的標頭名稱。 如需詳細資訊,請參閱轉送標頭中介軟體選項使用不同標頭名稱之 Proxy 的組態

IIS/IIS Express 和 ASP.NET Core 模組

當應用程式是在 IIS 和 ASP.NET Core 模組的後方進行處理序外裝載時,IIS 整合中介軟體預設會啟用「轉送的標頭中介軟體」。 由於有與轉送標頭相關的信任考量 (例如 IP 詐騙),因此「轉送的標頭中介軟體」在啟用後會先在中介軟體管線中搭配 ASP.NET Core 模組限定的設定來執行。 中介軟體會經設定來轉送 X-Forwarded-ForX-Forwarded-Proto 標頭,並限制成單一 localhost Proxy。 如果需要額外的設定,請參閱轉送的標頭中介軟體選項

其他 Proxy 伺服器和負載平衡器案例

除了在處理序外裝載時使用 IIS 整合之外,都未預設啟用「轉送的標頭中介軟體」。 必須啟用「轉送的標頭中介軟體」,應用程式才能使用 UseForwardedHeaders 來處理轉送的標頭。 啟用此中介軟體之後,如果未將任何 ForwardedHeadersOptions 指定給中介軟體,則預設的 ForwardedHeadersOptions.ForwardedHeaders 會是 ForwardedHeaders.None

搭配 ForwardedHeadersOptions 設定中介軟體,以在 Startup.ConfigureServices 中轉送 X-Forwarded-ForX-Forwarded-Proto 標頭。

轉送的標頭中介軟體順序

應在其他中介軟體之前執行轉送標頭中介軟體。 這種排序可確保依賴轉送標頭資訊的中介軟體可以耗用用於處理的標頭值。 轉送標頭中介軟體可以在診斷和錯誤處理之後執行,但必須在呼叫 UseHsts 之前執行:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders =
                ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseForwardedHeaders();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseForwardedHeaders();
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

或者,在診斷之前呼叫 UseForwardedHeaders

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseForwardedHeaders();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

注意

若未在 Startup.ConfigureServices 中指定 ForwardedHeadersOptions,或未使用 UseForwardedHeaders 直接指定到擴充方法,則要轉送的預設標頭是 ForwardedHeaders.None。 必須使用要轉送的標頭設定 ForwardedHeaders 屬性。

Nginx 組態

若要轉送 X-Forwarded-ForX-Forwarded-Proto 標頭,請參閱使用 Nginx 在 Linux 上裝載 ASP.NET Core。 如需詳細資訊,請參閱 NGINX:使用轉送的標頭

Apache 組態

X-Forwarded-For 會自動新增 (請參閱 Apache 模組 mod_proxy:反向 Proxy 要求標頭)。 如需如何轉送 X-Forwarded-Proto 標頭的資訊,請參閱使用 Apache 在 Linux 上裝載 ASP.NET Core

轉送的標頭中介軟體選項

ForwardedHeadersOptions 會控制轉送標頭中介軟體的行為。 下列範例會變更預設值:

  • 將轉送標頭中的項目數限制為 2
  • 新增已知的 Proxy 位址 127.0.10.1
  • 將轉送標頭名稱從預設的 X-Forwarded-For 變更為 X-Forwarded-For-My-Custom-Header-Name
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
選項 描述
AllowedHosts 依據 X-Forwarded-Host 標頭將主機限制成所提供的值。
  • 比較值時,會使用序數忽略大小寫的方式來比較。
  • 必須排除連接埠號碼。
  • 如果清單空白,即表示允許所有主機。
  • 最上層的萬用字元 * 代表會允許所有非空白的主機。
  • 允許使用子網域萬用字元,但不會比對出根網域。 例如,*.contoso.com 會比對出子網域 foo.contoso.com,但不會比對出根網域 contoso.com
  • 允許使用 Unicode 主機名稱,但會轉換成 Punycode 來進行比對。
  • IPv6 addresses 必須包含週框方括號,並採用慣例格式 (例如 [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789])。 IPv6 位址並未特別設計成會檢查不同格式間是否具有邏輯相等性,因此不會執行標準化。
  • 如果無法限制可允許的主機,可能會讓攻擊者偽造服務所產生的連結。
預設值是空的 IList<string>
ForwardedForHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedForHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-For 標頭,而使用其他標頭轉送資訊時,會使用此選項。

預設值為 X-Forwarded-For
ForwardedHeaders 識別應該處理哪個轉送子。 如需適用的欄位清單,請參閱 ForwardedHeaders 列舉。 指派給此屬性的一般值為 ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto

預設值為 ForwardedHeaders.None
ForwardedHostHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedHostHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-Host 標頭,而使用其他標頭轉送資訊時,會使用此選項。

預設值為 X-Forwarded-Host
ForwardedProtoHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedProtoHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-Proto 標頭,而使用其他標頭轉送資訊時,會使用此選項。

預設值為 X-Forwarded-Proto
ForwardLimit 限制所處理標頭中的項目數。 設定為 null 可停用限制,但應該只有在已設定 KnownProxiesKnownNetworks 的情況下,才這樣做。 設置非 null 值是一種預防措施 (但不是保證),以防止設定不正確的 Proxy 和來自網路上的旁路惡意要求。

「轉送的標頭中介軟體」會以相反順序 (從右至左) 處理標頭。 如果使用預設值 (1),除非 ForwardLimit 的值增加,否則只會處理標頭中最右邊的值。

預設值為 1
KnownNetworks 可從中接受轉送標頭的已知網路位址範圍。 請使用無類別網域間路由 (CIDR) 標記法來提供 IP 範圍。

若伺服器使用雙模式通訊端,會以 IPv6 格式 (例如,IPv4 中的 10.0.0.1 在 IPv6 中以 ::ffff:10.0.0.1 表示) 提供 IPv4 位址。 請參閱 IPAddress.MapToIPv6。 透過查看 HttpContext.Connection.RemoteIpAddress 以判斷是否需要此格式。

預設值為包含單一 IPAddress.Loopback 項目的 IList<IPNetwork>。
KnownProxies 可從中接受轉送標頭的已知 Proxy 位址。 請使用 KnownProxies 來指定確切的相符 IP 位址。

若伺服器使用雙模式通訊端,會以 IPv6 格式 (例如,IPv4 中的 10.0.0.1 在 IPv6 中以 ::ffff:10.0.0.1 表示) 提供 IPv4 位址。 請參閱 IPAddress.MapToIPv6。 透過查看 HttpContext.Connection.RemoteIpAddress 以判斷是否需要此格式。

預設值為包含單一 IPAddress.IPv6Loopback 項目的 IList<IPAddress>。
OriginalForHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XOriginalForHeaderName 所指定的標頭。

預設值為 X-Original-For
OriginalHostHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XOriginalHostHeaderName 所指定的標頭。

預設值為 X-Original-Host
OriginalProtoHeaderName 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XOriginalProtoHeaderName 所指定的標頭。

預設值為 X-Original-Proto
RequireHeaderSymmetry 要求所處理 ForwardedHeadersOptions.ForwardedHeaders 的標頭值數目必須同步。

ASP.NET Core 1.x 中的預設值為 true。 ASP.NET Core 2.0 或更新版本中的預設值為 false

情節和使用案例

當可以新增轉送的標頭且所有要求都安全時

在某些情況下,可能無法將轉送的標頭新增至透過 Proxy 傳送給應用程式的要求。 如果 Proxy 強制要求所有公用外部要求都必須是 HTTPS,您可以在使用任何類型的中介軟體之前,先手動在 Startup.Configure 中設定該配置:

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next();
});

在開發或預備環境中,可以使用環境變數或其他組態設定來停用此程式碼。

處理路徑基底和變更要求路徑的 Proxy

有些 Proxy 會原封不動傳送路徑,但其中含有應移除才能正確路由傳送的應用程式基底路徑。 UsePathBaseExtensions.UsePathBase 中介軟體會將該路徑分割成 HttpRequest.Path,以及將應用程式基底路徑分割成 HttpRequest.PathBase

如果 /foo 是以 /foo/api/1 形式傳送之 Proxy 路徑的應用程式基底路徑,中介軟體就會使用下列命令,將 Request.PathBase 設定為 /foo,以及將 Request.Path 設定為 /api/1

app.UsePathBase("/foo");

反向再次呼叫應用程式時,則會重新套用原始路徑和路徑基底。 如需中介軟體順序處理的詳細資訊,請參閱 ASP.NET Core 中介軟體

如果 Proxy 會修剪路徑 (例如,將 /foo/api/1 轉送給 /api/1),請設定要求的 PathBase 屬性來修正重新導向和連結:

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next();
});

如果 Proxy 會新增路徑資料,請使用 StartsWithSegments 並指派給 Path 屬性,以捨棄部分路徑來修正重新導向和連結:

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next();
});

使用不同標頭名稱之 Proxy 的組態

如果 Proxy 未使用名為 X-Forwarded-ForX-Forwarded-Proto 的標頭轉送 Proxy 位址/連接埠與原始配置資訊,請設定 ForwardedForHeaderNameForwardedProtoHeaderName 選項,以符合 Proxy 使用的標頭名稱:

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-Proto_Header";
});

轉送 Linux 和非 IIS 反向 Proxy 的配置

如果部署至 Azure Linux App Service、Azure Linux 虛擬機器 (VM),或 IIS 之外的任何其他反向 Proxy 後端,則呼叫 UseHttpsRedirectionUseHsts 的這些應用程式會使站台進入無限迴圈。 反向 Proxy 會終止 TLS,且正確的要求配置不知道有 Kestrel。 OAuth 和 OIDC 在此組態中也失敗,因為它們會產生不正確的重新導向。 UseIISIntegration 在 IIS 後端執行時,會新增並設定轉送標頭中介軟體,但沒有相符的 Linux (Apache 或 Nginx 整合) 自動組態。

若要從非 IIS 案例的 Proxy 轉送配置,請新增並設定轉送標頭中介軟體。 在 Startup.ConfigureServices 中,使用下列程式碼:

// using Microsoft.AspNetCore.HttpOverrides;

if (string.Equals(
    Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"), 
    "true", StringComparison.OrdinalIgnoreCase))
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
            ForwardedHeaders.XForwardedProto;
        // Only loopback proxies are allowed by default.
        // Clear that restriction because forwarders are enabled by explicit 
        // configuration.
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
}

憑證轉送

Azure

若要設定 Azure App Service 進行憑證轉送,請參閱 為 Azure App Service 設定 TLS 相互驗證。 下列指引與設定 ASP.NET Core 應用程式有關。

Startup.Configure 中,於呼叫 app.UseAuthentication(); 之前新增下列程式碼:

app.UseCertificateForwarding();

設定憑證轉送中介軟體以指定 Azure 使用的標頭名稱。 在 Startup.ConfigureServices 中新增下列程式碼,以設定中介軟體從中建置憑證的標頭:

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "X-ARR-ClientCert");

其他 Web Proxy

如果使用的 Proxy 不是 IIS 或 Azure App Service 的應用程式要求路由 (ARR),請設定該 Proxy 以轉送其在 HTTP 標頭中收到的憑證。 在 Startup.Configure 中,於呼叫 app.UseAuthentication(); 之前新增下列程式碼:

app.UseCertificateForwarding();

設定憑證轉送中介軟體以指定標頭名稱。 在 Startup.ConfigureServices 中新增下列程式碼,以設定中介軟體從中建置憑證的標頭:

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");

如果 Proxy 不是使用 base64 編碼憑證 (和 Nginx 一樣),則請設定 HeaderConverter 選項。 請考慮 Startup.ConfigureServices 中的下列範例:

services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
    options.HeaderConverter = (headerValue) => 
    {
        var clientCertificate = 
           /* some conversion logic to create an X509Certificate2 */
        return clientCertificate;
    }
});

疑難排解

當標頭未如預期般傳送時,請啟用記錄功能。 如果記錄提供的資訊不足,無法針對問題進行疑難排解,則請列舉伺服器所收到的要求標頭。 使用內嵌中介軟體將要求標頭寫入應用程式回應或記錄標頭。

若要將標頭寫入至應用程式的回應,將下列終端機內嵌中介軟體緊接著放置於 Startup.Configure 中對 UseForwardedHeaders 的呼叫之後:

app.Run(async (context) =>
{
    context.Response.ContentType = "text/plain";

    // Request method, scheme, and path
    await context.Response.WriteAsync(
        $"Request Method: {context.Request.Method}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Path: {context.Request.Path}{Environment.NewLine}");

    // Headers
    await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");

    foreach (var header in context.Request.Headers)
    {
        await context.Response.WriteAsync($"{header.Key}: " +
            $"{header.Value}{Environment.NewLine}");
    }

    await context.Response.WriteAsync(Environment.NewLine);

    // Connection: RemoteIp
    await context.Response.WriteAsync(
        $"Request RemoteIp: {context.Connection.RemoteIpAddress}");
});

您可以寫入至記錄,而不是回應本文。 寫入至記錄可讓網站在偵錯時正常運作。

若要寫入記錄而不是回應本文:

app.Use(async (context, next) =>
{
    // Request method, scheme, and path
    _logger.LogDebug("Request Method: {Method}", context.Request.Method);
    _logger.LogDebug("Request Scheme: {Scheme}", context.Request.Scheme);
    _logger.LogDebug("Request Path: {Path}", context.Request.Path);

    // Headers
    foreach (var header in context.Request.Headers)
    {
        _logger.LogDebug("Header: {Key}: {Value}", header.Key, header.Value);
    }

    // Connection: RemoteIp
    _logger.LogDebug("Request RemoteIp: {RemoteIpAddress}", 
        context.Connection.RemoteIpAddress);

    await next();
});

處理後,X-Forwarded-{For|Proto|Host} 值會移至 X-Original-{For|Proto|Host}。 如果指定的標頭中有多個值,「轉送的標頭中介軟體」會以相反順序 (從右至左) 處理標頭。 預設的 ForwardLimit1 (一),因此除非 ForwardLimit 的值增加,否則只會處理標頭中最右邊的值。

要求的原始遠端 IP 必須符合 KnownProxiesKnownNetworks 清單中的項目,系統才會處理轉送的標頭。 這可藉由不接受來自不受信任 Proxy 的轉送子,以限制標頭詐騙行為。 當偵測到未知的 Proxy 時,記錄會指出 Proxy 的位址:

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

在上述範例中,10.0.0.100 是 Proxy 伺服器。 如果伺服器是信任的 Proxy,請將伺服器的 IP 位址新增至 Startup.ConfigureServices 中的 KnownProxies (或將信任的網路新增至 KnownNetworks)。 如需詳細資訊,請參閱轉送的標頭中介軟體選項一節。

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

重要

只允許信任的 Proxy 以及網路轉送標頭。 否則,IP 詐騙攻擊有可能發生。

其他資源