配置 ASP.NET Core 以使用代理服务器和负载均衡器
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
作者:Chris Ross
在 ASP.NET Core 的建议配置中,应用使用用于 IIS 的 ASP.NET Core 模块 (ANCM)、Nginx 或 Apache 进行托管。 代理服务器、负载均衡器和其他网络设备通常会在请求到达应用之前隐藏有关请求的信息:
- 当通过 HTTP 代理 HTTPS 请求时,原方案 (HTTPS) 将丢失,并且必须在标头中转接。
- 由于应用收到来自代理的请求,而不是 Internet 或公司网络上请求的真实源,因此原始客户端 IP 地址也必须在标头中转接。
此信息在请求处理中可能很重要,例如在重定向、身份验证、链接生成、策略评估和客户端地理位置中。
对于面向 Web 场运行的应用,应阅读 Web 场中的主机 ASP.NET Core。
转接头
按照约定,代理转接 HTTP 标头中的信息。
Header | 说明 |
---|---|
X-Forwarded-For (XFF) |
保存代理链中关于发起请求的客户端和后续代理的信息。 该参数可能包含 IP 地址以及可选端口号。 在代理服务器链中,第一个参数表示最初发出请求的客户端。 后续代理标识符以此类推。 链中的最后一个代理不在参数列表中。 最后一个代理的 IP 地址以及可选的端口号可用作传输层的远程 IP 地址。 |
X-Forwarded-Proto (XFP) |
原方案的值,HTTP 或 HTTPS。 如果请求已遍历多个代理,则该值也可以是方案列表。 |
X-Forwarded-Host (XFH) |
主机标头字段的原始值。 代理通常不会修改主机标头。 有关特权提升漏洞的信息,请参阅 Microsoft 安全公告 CVE-2018-0787,该漏洞影响代理未验证或将主机标头限制为已知正确值的系统。 |
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
中有多个值,则此过程可能会多次重复,从而导致将多个值(包括原始RemoteIpAddress/Host/Scheme/PathBase
)移动到X-Original-*
。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 问题。
可以配置转接头中间件默认设置。 对于默认设置:
- 应用和请求源之间只有一个代理。
- 仅将环回地址配置为已知代理和已知网络。
- 转发标头被命名为
X-Forwarded-For
、X-Forwarded-Proto
、X-Forwarded-Host
和X-Forwarded-Prefix
。 ForwardedHeaders
值为ForwardedHeaders.None
,必须在此设置所需的转发器才能启用中间件。
并非所有网络设备均可在没有其他配置的情况下添加 X-Forwarded-For
和 X-Forwarded-Proto
标头。 如果代理请求在到达应用时未包含这些标头,请查阅设备制造商提供的指导。 如果设备使用 X-Forwarded-For
和 X-Forwarded-Proto
以外的其他标头名称,请设置 ForwardedForHeaderName 和 ForwardedProtoHeaderName 选项,使其与设备所用的标头名称相匹配。 有关详细信息,请参阅转接头中间件选项和配置使用不同标头名称的代理。
IIS/IIS Express 和 ASP.NET Core 模块
当应用托管在 IIS 和用于 IIS 的 ASP.NET Core 模块 (ANCM) 后方的进程外时,转接头中间件由 IIS 集成中间件默认启用。 转接头中间件被激活以首先在中间件管道中运行,并具有特定于 ASP.NET Core 模块的受限配置。 受限配置是由于转接头的信任问题(例如 IP 欺骗)导致的。 中间件配置为转接 X-Forwarded-For
和 X-Forwarded-Proto
标头,并且被限制到单个本地 localhost 代理。 如果需要其他配置,请参阅转接头中间件选项。
其他代理服务器和负载均衡器方案
除在进程外托管时使用 IIS 集成之外,不会默认启用转接头中间件。 必须启用应用的转接头中间件才能处理带有 UseForwardedHeaders 的转接头。 启用中间件后,如果没有为中间件指定 ForwardedHeadersOptions,那么默认的 ForwardedHeadersOptions 是 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 直接应用到扩展方法,则要转接的默认标头为 ForwardedHeadersOptions。 必须为 ForwardedHeaders 属性配置要转接的标头。
Nginx 配置
要转接 X-Forwarded-For
和 X-Forwarded-Proto
标头,请参阅X-Forwarded-For
。 有关详细信息,请参阅 NGINX:使用转发标头。
Apache 配置
X-Forwarded-For
会自动添加。 有关详细信息,请参阅 Apache 模块 mod_proxy:反向代理请求标头。
转接头中间件选项
ForwardedHeadersOptions 控制ForwardedHeadersOptions的行为。 以下示例更改了默认值:
- 将转接头中的条目数量限制为
2
。 - 添加已知的代理地址
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 指定的标头。 如果代理/转发器不使用 X-Forwarded-For 标头,但使用一些其他标头来转发信息,则使用此选项。默认值为 X-Forwarded-For 。 |
ForwardedHeaders | 标识应处理的转发器。 对于应用的字段的列表,请参阅 ForwardedHeaders 枚举。 分配给此属性的典型值为 ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto 。默认值是 ForwardedHeaders.None。 |
ForwardedHostHeaderName | 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedHostHeaderName 指定的标头。 如果代理/转发器不使用 X-Forwarded-Host 标头,但使用一些其他标头来转发信息,则使用此选项。默认值为 X-Forwarded-Host 。 |
ForwardedProtoHeaderName | 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedProtoHeaderName 指定的标头。 如果代理/转发器不使用 X-Forwarded-Proto 标头,但使用一些其他标头来转发信息,则使用此选项。默认值为 X-Forwarded-Proto 。 |
ForwardLimit | 限制所处理的标头中的条目数。 设置为 null 以禁用此限制,但仅应在已配置 KnownProxies 或 KnownNetworks 的情况下执行此操作。 设置非 null 值是一种预防措施(但不是保证),防止错误配置的代理和恶意请求从网络的侧通道到达。转接头中间件按照从右向左的相反顺序处理标头。 如果使用默认值 ( 1 ),则只会处理标头最右侧的值,除非增加 ForwardLimit 的值。默认值为 1 。 |
KnownNetworks | 从中接受转接头的已知网络的地址范围。 使用无类别域际路由选择 (CIDR) 表示法提供 IP 范围。 如果服务器使用双模式套接字,则采用 IPv6 格式提供 IPv4 地址(例如,IPv4 格式的 10.0.0.1 以 IPv6 格式表示为 ::ffff:10.0.0.1 )。 请参阅 IPAddress.MapToIPv6。 通过查看 HttpContext.Connection.RemoteIpAddress 来确定是否需要采用此格式。默认值是 IList <IPNetwork>,其中包含 new IPNetwork(IPAddress.Loopback, 8) 的单个条目。 |
KnownProxies | 从中接受转接头的已知代理的地址。 使用 KnownProxies 指定精确的 IP 地址匹配。如果服务器使用双模式套接字,则采用 IPv6 格式提供 IPv4 地址(例如,IPv4 格式的 10.0.0.1 以 IPv6 格式表示为 ::ffff:10.0.0.1 )。 请参阅 IPAddress.MapToIPv6。 通过查看 HttpContext.Connection.RemoteIpAddress 来确定是否需要采用此格式。默认值是 IList <IPAddress>,其中包含 IPAddress.IPv6Loopback 的单个条目。 |
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 。 |
方案与使用案例
无法添加转接头并且所有请求都安全的情况
在某些情况下,可能无法将转接头添加到代理到应用的请求中。 如果代理强制将所有公共外部请求执行为 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();
处理改变请求路径的基路径和代理
一些代理可完整地传递路径,但是会删除应用基路径以便路由可正常工作。 UsePathBaseExtensions.UsePathBase 中间件将路径拆分为 HttpRequest.Path,将应用基路径拆分为 HttpRequest.PathBase。
如果 /foo
是作为 /foo/api/1
传递的代理路径的应用基路径,则中间件使用以下命令将 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 中间件。
如果代理剪裁路径(例如,将 /foo/api/1
转接到 /api/1
),请通过设置请求的 /foo/api/1
属性来修复重定向和链接:
app.Use((context, next) =>
{
context.Request.PathBase = new PathString("/foo");
return next(context);
});
如果代理要添加路径数据,请使用 StartsWithSegments 并分配给 Path 属性,从而放弃部分路径以修复重定向和链接:
app.Use((context, next) =>
{
if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
{
context.Request.Path = remainder;
}
return next(context);
});
配置使用不同标头名称的代理
如果代理不使用名为 X-Forwarded-For
和 X-Forwarded-Proto
的标头来转发代理地址/端口和原始架构信息,则设置 ForwardedForHeaderName 和 ForwardedProtoHeaderName 选项,使其与代理所用的标头名称相匹配:
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 反向代理的方案
如果将站点部署到 Azure Linux 应用服务、Azure Linux 虚拟机 (VM),或者部署到除 IIS 之外的任何其他反向代理之后,调用 UseHttpsRedirection 和 UseHsts 的应用都会使站点进入无限循环。 反向代理终止 TLS,并且 Kestrel 未发现正确的请求方案。 由于 OAuth 和 OIDC 生成了错误的重定向,因此它们在此配置中也会出现故障。 UseIISIntegration 在 IIS 之后运行时会添加和配置转接头中间件,但 Linux(Apache 或 Nginx 集成)没有匹配的自动配置。
要从非 IIS 方案中的代理中转发方案,请通过将 ASPNETCORE_FORWARDEDHEADERS_ENABLED
设置为 true
来启用转发标头中间件。 警告:此标志使用专为云环境设计的设置,并且不启用功能(例如 KnownProxies option
)来限制接受哪些 IP 转发器。
转发证书
Azure
若要为证书转发配置 Azure 应用服务,请参阅为 Azure 应用服务配置 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 代理
如果使用的代理不是 IIS 或 Azure 应用服务的应用程序请求路由 (ARR),请配置代理,以便转发其在 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();
如果代理不在对证书进行 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
级别的debug
和 HTTP 请求日志记录。 必须在 UseForwardedHeaders 之后调用 UseHttpLogging:
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 列表中的条目匹配。 这通过不接受来自不受信任的代理的转发器来限制标头欺骗。 检测到未知代理时,日志记录会指出代理的地址:
September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321
在上述示例中,10.0.0.100 是代理服务器。 如果该服务器是受信任的代理,请将服务器的 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"
}
}
}
重要
仅允许受信任的代理和网络转接头。 否则,可能会受到 IP 欺骗攻击。
其他资源
在 ASP.NET Core 推荐配置中,应用使用 IIS/ASP.NET Core 模块、Nginx 或 Apache 进行托管。 代理服务器、负载均衡器和其他网络设备通常会在请求到达应用之前隐藏有关请求的信息:
- 当通过 HTTP 代理 HTTPS 请求时,原方案 (HTTPS) 将丢失,并且必须在标头中转接。
- 由于应用收到来自代理的请求,而不是 Internet 或公司网络上请求的真实源,因此原始客户端 IP 地址也必须在标头中转接。
此信息在请求处理中可能很重要,例如在重定向、身份验证、链接生成、策略评估和客户端地理位置中。
转接头
按照约定,代理转接 HTTP 标头中的信息。
Header | 描述 |
---|---|
X-Forwarded-For | 保存代理链中关于发起请求的客户端和后续代理的信息。 该参数可能包含 IP 地址(以及可选端口号)。 在代理服务器链中,第一个参数表示最初发出请求的客户端。 后续代理标识符以此类推。 链中的最后一个代理不在参数列表中。 最后一个代理的 IP 地址以及可选的端口号可用作传输层的远程 IP 地址。 |
X-Forwarded-Proto | 原方案的值 (HTTP/HTTPS)。 如果请求已遍历多个代理,则该值也可以是方案列表。 |
X-Forwarded-Host | 主机标头字段的原始值。 代理通常不会修改主机标头。 有关特权提升漏洞的信息,请参阅 Microsoft 安全公告 CVE-2018-0787,该漏洞影响代理未验证或将主机标头限制为已知正确值的系统。 |
转接头中间件 (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 问题。
可以配置转接头中间件默认设置。 对于默认设置:
- 应用和请求源之间只有一个代理。
- 仅将环回地址配置为已知代理和已知网络。
- 转接头被命名为
X-Forwarded-For
和X-Forwarded-Proto
。 ForwardedHeaders
值为ForwardedHeaders.None
,必须在此设置所需的转发器才能启用中间件。
并非所有网络设备均可在没有其他配置的情况下添加 X-Forwarded-For
和 X-Forwarded-Proto
标头。 如果代理请求在到达应用时未包含这些标头,请查阅设备制造商提供的指导。 如果设备使用 X-Forwarded-For
和 X-Forwarded-Proto
以外的其他标头名称,请设置 ForwardedForHeaderName 和 ForwardedProtoHeaderName 选项,使其与设备所用的标头名称相匹配。 有关详细信息,请参阅转接头中间件选项和配置使用不同标头名称的代理。
IIS/IIS Express 和 ASP.NET Core 模块
当应用托管在 IIS 和 ASP.NET Core 模块后方的进程外时,转接头中间件由 IIS 集成中间件默认启用。 由于转接头的信任问题(例如,IP 欺骗),转接头中间件被激活为首先在中间件管道中运行,并具有特定于 ASP.NET Core 模块的受限配置。 中间件配置为转接 X-Forwarded-For
和 X-Forwarded-Proto
标头,并且被限制到单个本地 localhost 代理。 如果需要其他配置,请参阅转接头中间件选项。
其他代理服务器和负载均衡器方案
除在进程外托管时使用 IIS 集成之外,不会默认启用转接头中间件。 必须启用应用的转接头中间件才能处理带有 UseForwardedHeaders 的转接头。 启用中间件后,如果没有为中间件指定 ForwardedHeadersOptions,那么默认的 ForwardedHeadersOptions 是 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 直接指定到扩展方法,则要转接的默认标头为 ForwardedHeadersOptions。 必须为 ForwardedHeaders 属性配置要转接的标头。
Nginx 配置
要转接 X-Forwarded-For
和 X-Forwarded-Proto
标头,请参阅X-Forwarded-For
。 有关详细信息,请参阅 NGINX:使用转发标头。
Apache 配置
X-Forwarded-For
将会自动添加(请参阅 X-Forwarded-For
)。
转接头中间件选项
ForwardedHeadersOptions 控制转接头中间件的行为。 以下示例更改了默认值:
- 将转接头中的条目数量限制为
2
。 - 添加已知的代理地址
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 指定的标头。 如果代理/转发器不使用 X-Forwarded-For 标头,但使用一些其他标头来转发信息,则使用此选项。默认为 X-Forwarded-For 。 |
ForwardedHostHeaderName | 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedHostHeaderName 指定的标头。 如果代理/转发器不使用 X-Forwarded-Host 标头,但使用一些其他标头来转发信息,则使用此选项。默认值为 X-Forwarded-Host 。 |
ForwardedProtoHeaderName | 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedProtoHeaderName 指定的标头。 如果代理/转发器不使用 X-Forwarded-Proto 标头,但使用一些其他标头来转发信息,则使用此选项。默认为 X-Forwarded-Proto 。 |
ForwardedPrefixHeaderName | 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedPrefixHeaderName 指定的标头。 如果代理/转发器不使用 X-Forwarded-Prefix 标头,但使用一些其他标头来转发信息,则使用此选项。默认为 X-Forwarded-Prefix 。 |
ForwardLimit | 限制所处理的标头中的条目数。 设置为 null 以禁用此限制,但仅应在已配置 KnownProxies 或 KnownNetworks 的情况下执行此操作。 设置非 null 值是一种预防措施(但不是保证),防止错误配置的代理和恶意请求从网络的侧通道到达。转接头中间件按照从右向左的相反顺序处理标头。 如果使用默认值 ( 1 ),则只会处理标头最右侧的值,除非增加 ForwardLimit 的值。默认值为 1 。 |
KnownNetworks | 从中接受转接头的已知网络的地址范围。 使用无类别域际路由选择 (CIDR) 表示法提供 IP 范围。 如果服务器使用双模式套接字,则采用 IPv6 格式提供 IPv4 地址(例如,IPv4 格式的 10.0.0.1 以 IPv6 格式表示为 ::ffff:10.0.0.1 )。 请参阅 IPAddress.MapToIPv6。 通过查看 HttpContext.Connection.RemoteIpAddress 来确定是否需要采用此格式。默认值是 IList <IPNetwork>,其中包含 new IPNetwork(IPAddress.Loopback, 8) 的单个条目。 |
KnownProxies | 从中接受转接头的已知代理的地址。 使用 KnownProxies 指定精确的 IP 地址匹配。如果服务器使用双模式套接字,则采用 IPv6 格式提供 IPv4 地址(例如,IPv4 格式的 10.0.0.1 以 IPv6 格式表示为 ::ffff:10.0.0.1 )。 请参阅 IPAddress.MapToIPv6。 通过查看 HttpContext.Connection.RemoteIpAddress 来确定是否需要采用此格式。默认值是 IList <IPAddress>,其中包含 IPAddress.IPv6Loopback 的单个条目。 |
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 。 |
方案与使用案例
无法添加转接头并且所有请求都安全的情况
在某些情况下,可能无法将转接头添加到代理到应用的请求中。 如果代理强制将所有公共外部请求执行为 HTTPS,则在使用任何类型的中间件之前,可以在 Startup.Configure
中手动设置该方案:
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next();
});
此代码可以通过环境变量或者开发环境或过渡环境中的其他配置设置来禁用。
处理改变请求路径的基路径和代理
一些代理可完整地传递路径,但是会删除应用基路径以便路由可正常工作。 UsePathBaseExtensions.UsePathBase 中间件将路径拆分为 HttpRequest.Path,将应用基路径拆分为 HttpRequest.PathBase。
如果 /foo
是作为 /foo/api/1
传递的代理路径的应用基路径,则中间件使用以下命令将 Request.PathBase
设置为 /foo
,将 Request.Path
设置为 /api/1
:
app.UsePathBase("/foo");
当再次反向调用中间件时,将重新应用原始路径和基路径。 有关中间件处理顺序的详细信息,请参阅 ASP.NET Core 中间件。
如果代理剪裁路径(例如,将 /foo/api/1
转接到 /api/1
),请通过设置请求的 /foo/api/1
属性来修复重定向和链接:
app.Use((context, next) =>
{
context.Request.PathBase = new PathString("/foo");
return next();
});
如果代理要添加路径数据,请使用 StartsWithSegments 并分配给 Path 属性,从而放弃部分路径以修复重定向和链接:
app.Use((context, next) =>
{
if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
{
context.Request.Path = remainder;
}
return next();
});
配置使用不同标头名称的代理
如果代理不使用名为 X-Forwarded-For
和 X-Forwarded-Proto
的标头来转发代理地址/端口和原始架构信息,则设置 ForwardedForHeaderName 和 ForwardedProtoHeaderName 选项,使其与代理所用的标头名称相匹配:
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 反向代理的方案
如果将站点部署到 Azure Linux 应用服务、Azure Linux 虚拟机 (VM),或者部署到除 IIS 之外的任何其他反向代理之后,调用 UseHttpsRedirection 和 UseHsts 的应用都会使站点进入无限循环。 反向代理终止 TLS,并且 Kestrel 未发现正确的请求方案。 由于 OAuth 和 OIDC 生成了错误的重定向,因此它们在此配置中也会出现故障。 UseIISIntegration 在 IIS 之后运行时会添加和配置转接头中间件,但 Linux(Apache 或 Nginx 集成)没有匹配的自动配置。
要从非 IIS 方案中的代理中转发方案,请添加并配置转接头中间件。 在 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 应用服务,请参阅为 Azure 应用服务配置 TLS 相互身份验证。 以下指南与配置 ASP.NET Core 应用相关。
在 Startup.Configure
中,在调用 app.UseAuthentication();
前添加以下代码:
app.UseCertificateForwarding();
配置证书转发中间件,以指定 Azure 使用的标头名称。 在 Startup.ConfigureServices
中,添加以下代码来配置中间件从中生成证书的标头:
services.AddCertificateForwarding(options =>
options.CertificateHeader = "X-ARR-ClientCert");
其他 Web 代理
如果使用的代理不是 IIS 或 Azure 应用服务的应用程序请求路由 (ARR),请配置代理,以便转发其在 HTTP 标头中收到的证书。 在 Startup.Configure
中,在调用 app.UseAuthentication();
前添加以下代码:
app.UseCertificateForwarding();
配置证书转发中间件,以指定标头名称。 在 Startup.ConfigureServices
中,添加以下代码来配置中间件从中生成证书的标头:
services.AddCertificateForwarding(options =>
options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");
如果代理不在对证书进行 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
类中,如ILogger<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
列表中的条目匹配。 这通过不接受来自不受信任的代理的转发器来限制标头欺骗。 检测到未知代理时,日志记录会指出代理的地址:
September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321
在上述示例中,10.0.0.100 是代理服务器。 如果该服务器是受信任的代理,请将服务器的 IP 地址添加到 Startup.ConfigureServices
中的 KnownProxies
(或将受信任的网络添加到 KnownNetworks
)。 有关详细信息,请参阅转接头中间件选项部分。
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});
重要
仅允许受信任的代理和网络转接头。 否则,可能会受到 IP 欺骗攻击。