設定 ASP.NET Core 以處理 Proxy 伺服器和負載平衡器
注意
這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本。
作者: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 不會驗證或限制主機標頭為已知有效值的系統。 |
X-Forwarded-Prefix |
用戶端所要求的原始基底路徑。 對於要正確產生 URL、重新導向或連結回用戶端的應用程式,此標頭可能很有用。 |
轉送標頭中介軟體 ForwardedHeadersMiddleware 會讀取這些標頭,並將其填入 HttpContext 上的相關聯欄位。
中介軟體會更新:
HttpContext.Connection.RemoteIpAddress
: 使用X-Forwarded-For
標頭值進行設定。 額外的設定會影響中介軟體設定RemoteIpAddress
的方式。 如需詳細資料,請參閱轉送的標頭中介軟體選項。 取用的值會從X-Forwarded-For
中移除,而且HttpContext.Connection.RemoteIpAddress
的舊值會保存在X-Original-For
中。 注意: 如果X-Forwarded-For/Proto/Host/Prefix
中有多個值,則此流程可能會重複數次,導致數個值移至X-Original-*
,包括原始RemoteIpAddress/Host/Scheme/PathBase
。HttpContext.Request.Scheme
: 使用X-Forwarded-Proto
標頭值進行設定。 取用的值會從X-Forwarded-Proto
中移除,而且HttpContext.Request.Scheme
的舊值會保存在X-Original-Proto
中。HttpContext.Request.Host
: 使用X-Forwarded-Host
標頭值進行設定。 取用的值會從X-Forwarded-Host
中移除,而且HttpContext.Request.Host
的舊值會保存在X-Original-Host
中。HttpContext.Request.PathBase
: 使用X-Forwarded-Prefix
標頭值進行設定。 取用的值會從X-Forwarded-Prefix
中移除,而且HttpContext.Request.PathBase
的舊值會保存在X-Original-Prefix
中。
如需上述內容的詳細資訊,請參閱此 GitHub 問題。
您可以設定「轉送的標頭中介軟體」的預設設定。 對於預設設定:
- 在應用程式與要求的來源之間只有「一個 Proxy」。
- 針對已知的 Proxy 和已知的網路,只會設定回送位址。
- 轉送標頭命名為
X-Forwarded-For
、X-Forwarded-Proto
、X-Forwarded-Host
和X-Forwarded-Prefix
。 ForwardedHeaders
值為ForwardedHeaders.None
,必須在這裡設定所需的轉寄站,才能啟用中介軟體。
並非所有網路設備在新增 X-Forwarded-For
和 X-Forwarded-Proto
標頭時都不含額外組態。 如果透過 Proxy 傳送的要求在觸達應用程式時未包含這些標頭,請向您的設備製造商尋求指引。 如果設備使用的名稱不是 X-Forwarded-For
和 X-Forwarded-Proto
,請設定 ForwardedForHeaderName 與 ForwardedProtoHeaderName 選項,以符合設備使用的標頭名稱。 如需詳細資訊,請參閱轉送標頭中介軟體選項和使用不同標頭名稱之 Proxy 的組態。
IIS/IIS Express 和 ASP.NET Core 模組
當應用程式是在 IIS 和 適用於 IIS 的 ASP.NET Core 模組 (ANCM) 的後方進行跨處理序裝載時,IIS 整合中介軟體預設會啟用「轉送標頭中介軟體」。 「轉送標頭中介軟體」在啟用後會先在中介軟體管線中搭配 ASP.NET Core 模組限定的組態來執行。 限定的組態是基於有與轉送標頭相關的信任考量,例如 IP 詐騙。 中介軟體會經設定來轉送 X-Forwarded-For
和 X-Forwarded-Proto
標頭,並限制成單一 localhost Proxy。 如果需要額外的設定,請參閱轉送的標頭中介軟體選項。
其他 Proxy 伺服器和負載平衡器案例
除了在跨處理序裝載時使用 IIS 整合之外,都未預設啟用轉送標頭中介軟體。 必須啟用「轉送的標頭中介軟體」,應用程式才能使用 UseForwardedHeaders 來處理轉送的標頭。 啟用此中介軟體之後,如果未將任何 ForwardedHeadersOptions 指定給中介軟體,則預設的 ForwardedHeadersOptions.ForwardedHeaders 會是 ForwardedHeaders.None。
使用 ForwardedHeadersOptions 設定中介軟體,以轉送 X-Forwarded-For
與 X-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-For
和 X-Forwarded-Proto
標頭,請參閱使用 Nginx 在 Linux 上裝載 ASP.NET Core。 如需詳細資訊,請參閱 NGINX:使用轉送的標頭。
Apache 組態
X-Forwarded-For
會自動新增。 如需詳細資訊,請參閱 Apache 模組 mod_proxy:反向 Proxy 要求標頭。
轉送的標頭中介軟體選項
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 標頭將主機限制成所提供的值。
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 可停用限制,但應該只有在已設定 KnownProxies 或 KnownNetworks 的情況下,才這樣做。 設定非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 以判斷是否需要此格式。預設值為包含單一 new IPNetwork(IPAddress.Loopback, 8) 項目的 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-For
和 X-Forwarded-Proto
的標頭轉送 Proxy 位址/連接埠與原始配置資訊,請設定 ForwardedForHeaderName 與 ForwardedProtoHeaderName 選項,以符合 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 後端,則呼叫 UseHttpsRedirection 和 UseHsts 的這些應用程式會使站台進入無限迴圈。 反向 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 應用程式有關。
- 設定憑證轉送中介軟體以指定 Azure 使用的標頭名稱。 新增下列程式碼,以設定中介軟體從中建置憑證的標頭。
- 在呼叫 UseAuthentication 之前呼叫 UseCertificateForwarding。
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 標頭中收到的憑證。
- 設定憑證轉送中介軟體以指定標頭名稱。 新增下列程式碼,以設定中介軟體從中建置憑證的標頭。
- 在呼叫 UseAuthentication 之前呼叫 UseCertificateForwarding。
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();
如果指定的標頭中有多個值,「轉送的標頭中介軟體」會以相反順序 (從右至左) 處理標頭。 預設的 ForwardLimit
是 1
(一),因此除非 ForwardLimit
的值增加,否則只會處理標頭中最右邊的值。
要求的原始遠端 IP 必須符合 KnownProxies 或 KnownNetworks 清單中的項目,系統才會處理轉送的標頭。 這可藉由不接受來自不受信任 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 上的相關聯欄位。
中介軟體會更新:
- HttpContext.Connection.RemoteIpAddress:使用
X-Forwarded-For
標頭值來設定。 額外的設定會影響中介軟體設定RemoteIpAddress
的方式。 如需詳細資料,請參閱轉送的標頭中介軟體選項。 取用的值會從X-Forwarded-For
中移除,而且舊值會保存在X-Original-For
中。 相同的模式會套用到其他標頭Host
和Proto
。 - HttpContext.Request.Scheme:使用
X-Forwarded-Proto
標頭值來設定。 - HttpContext.Request.Host:使用
X-Forwarded-Host
標頭值來設定。
如需上述內容的詳細資訊,請參閱此 GitHub 問題。
您可以設定「轉送的標頭中介軟體」的預設設定。 對於預設設定:
- 在應用程式與要求的來源之間只有「一個 Proxy」。
- 針對已知的 Proxy 和已知的網路,只會設定回送位址。
- 轉送標頭名稱為
X-Forwarded-For
和X-Forwarded-Proto
。 ForwardedHeaders
值為ForwardedHeaders.None
,必須在這裡設定所需的轉寄站,才能啟用中介軟體。
並非所有網路設備在新增 X-Forwarded-For
和 X-Forwarded-Proto
標頭時都不含額外組態。 如果透過 Proxy 傳送的要求在觸達應用程式時未包含這些標頭,請向您的設備製造商尋求指引。 如果設備使用的名稱不是 X-Forwarded-For
和 X-Forwarded-Proto
,請設定 ForwardedForHeaderName 與 ForwardedProtoHeaderName 選項,以符合設備使用的標頭名稱。 如需詳細資訊,請參閱轉送標頭中介軟體選項和使用不同標頭名稱之 Proxy 的組態。
IIS/IIS Express 和 ASP.NET Core 模組
當應用程式是在 IIS 和 ASP.NET Core 模組的後方進行處理序外裝載時,IIS 整合中介軟體預設會啟用「轉送的標頭中介軟體」。 由於有與轉送標頭相關的信任考量 (例如 IP 詐騙),因此「轉送的標頭中介軟體」在啟用後會先在中介軟體管線中搭配 ASP.NET Core 模組限定的設定來執行。 中介軟體會經設定來轉送 X-Forwarded-For
和 X-Forwarded-Proto
標頭,並限制成單一 localhost Proxy。 如果需要額外的設定,請參閱轉送的標頭中介軟體選項。
其他 Proxy 伺服器和負載平衡器案例
除了在處理序外裝載時使用 IIS 整合之外,都未預設啟用「轉送的標頭中介軟體」。 必須啟用「轉送的標頭中介軟體」,應用程式才能使用 UseForwardedHeaders 來處理轉送的標頭。 啟用此中介軟體之後,如果未將任何 ForwardedHeadersOptions 指定給中介軟體,則預設的 ForwardedHeadersOptions.ForwardedHeaders 會是 ForwardedHeaders.None。
搭配 ForwardedHeadersOptions 設定中介軟體,以在 Startup.ConfigureServices
中轉送 X-Forwarded-For
與 X-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-For
和 X-Forwarded-Proto
標頭,請參閱使用 Nginx 在 Linux 上裝載 ASP.NET Core。 如需詳細資訊,請參閱 NGINX:使用轉送的標頭。
Apache 組態
X-Forwarded-For
會自動新增 (請參閱 Apache 模組 mod_proxy:反向 Proxy 要求標頭)。
轉送的標頭中介軟體選項
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 標頭將主機限制成所提供的值。
IList<string> 。 |
ForwardedHeaders | 識別應該處理哪個轉送子。 如需適用的欄位清單,請參閱 ForwardedHeaders 列舉。 指派給此屬性的一般值為 ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto 。預設值為 ForwardedHeaders.None。 |
ForwardedForHeaderName | 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedForHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-For 標頭,而使用其他標頭轉送資訊時,會使用此選項。預設值為 X-Forwarded-For 。 |
ForwardedHostHeaderName | 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedHostHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-Host 標頭,而使用其他標頭轉送資訊時,會使用此選項。預設值為 X-Forwarded-Host 。 |
ForwardedProtoHeaderName | 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedProtoHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-Proto 標頭,而使用其他標頭轉送資訊時,會使用此選項。預設值為 X-Forwarded-Proto 。 |
ForwardedPrefixHeaderName | 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XForwardedPrefixHeaderName 所指定的標頭。 當 Proxy/轉寄站未使用 X-Forwarded-Prefix 標頭,而使用其他標頭轉送資訊時,會使用此選項。預設值為 X-Forwarded-Prefix 。 |
ForwardLimit | 限制所處理標頭中的項目數。 設定為 null 可停用限制,但應該只有在已設定 KnownProxies 或 KnownNetworks 的情況下,才這樣做。 設定非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 以判斷是否需要此格式。預設值為包含單一 new IPNetwork(IPAddress.Loopback, 8) 項目的 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 。 |
OriginalPrefixHeaderName | 使用此屬性所指定的標頭,而不是 ForwardedHeadersDefaults.XOriginalPrefixHeaderName 所指定的標頭。 預設值為 X-Original-Prefix 。 |
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-For
和 X-Forwarded-Proto
的標頭轉送 Proxy 位址/連接埠與原始配置資訊,請設定 ForwardedForHeaderName 與 ForwardedProtoHeaderName 選項,以符合 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 後端,則呼叫 UseHttpsRedirection 和 UseHsts 的這些應用程式會使站台進入無限迴圈。 反向 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}");
});
您可以寫入至記錄,而不是回應本文。 寫入至記錄可讓網站在偵錯時正常運作。
若要寫入記錄而不是回應本文:
- 將
ILogger<Startup>
插入至Startup
類別,如在啟動中建立記錄中所述。 - 將下列終端機內嵌中介軟體緊接著放置於
Startup.Configure
中對 UseForwardedHeaders 的呼叫之後。
app.Use(async (context, next) =>
{
// Request method, scheme, path, and base 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);
_logger.LogDebug("Request Path Base: {PathBase}", context.Request.PathBase);
// 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|Prefix}
值會移至 X-Original-{For|Proto|Host|Prefix}
。 如果指定的標頭中有多個值,「轉送的標頭中介軟體」會以相反順序 (從右至左) 處理標頭。 預設的 ForwardLimit
是 1
(一),因此除非 ForwardLimit
的值增加,否則只會處理標頭中最右邊的值。
要求的原始遠端 IP 必須符合 KnownProxies
或 KnownNetworks
清單中的項目,系統才會處理轉送的標頭。 這可藉由不接受來自不受信任 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 詐騙攻擊有可能發生。