配置 ASP.NET Core 以使用代理服务器和负载均衡器Configure ASP.NET Core to work with proxy servers and load balancers

作者:Luke LathamChris RossBy Luke Latham and Chris Ross

在 ASP.NET Core 推荐配置中,应用使用 IIS/ASP.NET Core 模块、Nginx 或 Apache 进行托管。In the recommended configuration for ASP.NET Core, the app is hosted using IIS/ASP.NET Core Module, Nginx, or Apache. 代理服务器、负载均衡器和其他网络设备通常会在请求到达应用之前隐藏有关请求的信息:Proxy servers, load balancers, and other network appliances often obscure information about the request before it reaches the app:

  • 当通过 HTTP 代理 HTTPS 请求时,原方案 (HTTPS) 将丢失,并且必须在标头中转接。When HTTPS requests are proxied over HTTP, the original scheme (HTTPS) is lost and must be forwarded in a header.
  • 由于应用收到来自代理的请求,而不是 Internet 或公司网络上请求的真实源,因此原始客户端 IP 地址也必须在标头中转接。Because an app receives a request from the proxy and not its true source on the Internet or corporate network, the originating client IP address must also be forwarded in a header.

此信息在请求处理中可能很重要,例如在重定向、身份验证、链接生成、策略评估和客户端地理位置中。This information may be important in request processing, for example in redirects, authentication, link generation, policy evaluation, and client geolocation.

转接头Forwarded headers

按照约定,代理转接 HTTP 标头中的信息。By convention, proxies forward information in HTTP headers.

HeaderHeader 说明Description
X-Forwarded-ForX-Forwarded-For 保存代理链中关于发起请求的客户端和后续代理的信息。Holds information about the client that initiated the request and subsequent proxies in a chain of proxies. 该参数可能包含 IP 地址(以及可选端口号)。This parameter may contain IP addresses (and, optionally, port numbers). 在代理服务器链中,第一个参数表示最初发出请求的客户端。In a chain of proxy servers, the first parameter indicates the client where the request was first made. 后续代理标识符以此类推。Subsequent proxy identifiers follow. 链中的最后一个代理不在参数列表中。The last proxy in the chain isn't in the list of parameters. 最后一个代理的 IP 地址以及可选的端口号可用作传输层的远程 IP 地址。The last proxy's IP address, and optionally a port number, are available as the remote IP address at the transport layer.
X-Forwarded-ProtoX-Forwarded-Proto 原方案的值 (HTTP/HTTPS)。The value of the originating scheme (HTTP/HTTPS). 如果请求已遍历多个代理,则该值也可以是方案列表。The value may also be a list of schemes if the request has traversed multiple proxies.
X-Forwarded-HostX-Forwarded-Host 主机标头字段的原始值。The original value of the Host header field. 代理通常不会修改主机标头。Usually, proxies don't modify the Host header. 有关特权提升漏洞的信息,请参阅 Microsoft 安全公告 CVE-2018-0787,该漏洞影响代理未验证或将主机标头限制为已知正确值的系统。See Microsoft Security Advisory CVE-2018-0787 for information on an elevation-of-privileges vulnerability that affects systems where the proxy doesn't validate or restrict Host headers to known good values.

来自 Microsoft.AspNetCore.HttpOverrides 包的转接头中间件读取这些标头,并填充 HttpContext 上的关联字段。The Forwarded Headers Middleware, from the Microsoft.AspNetCore.HttpOverrides package, reads these headers and fills in the associated fields on HttpContext.

中间件更新:The middleware updates:

可以配置转接头中间件默认设置Forwarded Headers Middleware default settings can be configured. 默认设置为:The default settings are:

  • 应用和请求源之间只有一个代理 。There is only one proxy between the app and the source of the requests.
  • 仅将环回地址配置为已知代理和已知网络。Only loopback addresses are configured for known proxies and known networks.
  • 转接头被命名为 X-Forwarded-ForX-Forwarded-ProtoThe forwarded headers are named X-Forwarded-For and X-Forwarded-Proto.

并非所有网络设备均可在没有其他配置的情况下添加 X-Forwarded-ForX-Forwarded-Proto 标头。Not all network appliances add the X-Forwarded-For and X-Forwarded-Proto headers without additional configuration. 如果代理请求在到达应用时未包含这些标头,请查阅设备制造商提供的指导。Consult your appliance manufacturer's guidance if proxied requests don't contain these headers when they reach the app. 如果设备使用 X-Forwarded-ForX-Forwarded-Proto 以外的其他标头名称,请设置 ForwardedForHeaderNameForwardedProtoHeaderName 选项,使其与设备所用的标头名称相匹配。If the appliance uses different header names than X-Forwarded-For and X-Forwarded-Proto, set the ForwardedForHeaderName and ForwardedProtoHeaderName options to match the header names used by the appliance. 有关详细信息,请参阅转接头中间件选项配置使用不同标头名称的代理For more information, see Forwarded Headers Middleware options and Configuration for a proxy that uses different header names.

IIS/IIS Express 和 ASP.NET Core 模块IIS/IIS Express and ASP.NET Core Module

当应用托管在 IIS 和 ASP.NET Core 模块后方的进程外时,转接头中间件由 IIS 集成中间件默认启用。Forwarded Headers Middleware is enabled by default by IIS Integration Middleware when the app is hosted out-of-process behind IIS and the ASP.NET Core Module. 由于转接头的信任问题(例如,IP 欺骗),转接头中间件被激活为首先在中间件管道中运行,并具有特定于 ASP.NET Core 模块的受限配置。Forwarded Headers Middleware is activated to run first in the middleware pipeline with a restricted configuration specific to the ASP.NET Core Module due to trust concerns with forwarded headers (for example, IP spoofing). 中间件配置为转接 X-Forwarded-ForX-Forwarded-Proto 标头,并且被限制到单个本地 localhost 代理。The middleware is configured to forward the X-Forwarded-For and X-Forwarded-Proto headers and is restricted to a single localhost proxy. 如果需要其他配置,请参阅转接头中间件选项If additional configuration is required, see the Forwarded Headers Middleware options.

其他代理服务器和负载均衡器方案Other proxy server and load balancer scenarios

除在进程外托管时使用 IIS 集成之外,不会默认启用转接头中间件。Outside of using IIS Integration when hosting out-of-process, Forwarded Headers Middleware isn't enabled by default. 必须启用应用的转接头中间件才能处理带有 UseForwardedHeaders 的转接头。Forwarded Headers Middleware must be enabled for an app to process forwarded headers with UseForwardedHeaders. 启用中间件后,如果没有为中间件指定 ForwardedHeadersOptions,那么默认的 ForwardedHeadersOptions.ForwardedHeadersForwardedHeaders.NoneAfter enabling the middleware if no ForwardedHeadersOptions are specified to the middleware, the default ForwardedHeadersOptions.ForwardedHeaders are ForwardedHeaders.None.

为中间件配置 ForwardedHeadersOptions 以转接 Startup.ConfigureServices 中的 X-Forwarded-ForX-Forwarded-Proto 标头。Configure the middleware with ForwardedHeadersOptions to forward the X-Forwarded-For and X-Forwarded-Proto headers in Startup.ConfigureServices. 调用其他中间件之前,先调用 Startup.Configure 中的 UseForwardedHeaders 方法:Invoke the UseForwardedHeaders method in Startup.Configure before calling other middleware:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

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

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

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

    app.UseStaticFiles();
    // In ASP.NET Core 1.x, replace the following line with: app.UseIdentity();
    app.UseAuthentication();
    app.UseMvc();
}

备注

如果没有在 Startup.ConfigureServices 中指定任何 ForwardedHeadersOptions,或未使用 UseForwardedHeaders 直接指定到扩展方法,则要转接的默认标头为 ForwardedHeaders.NoneIf no ForwardedHeadersOptions are specified in Startup.ConfigureServices or directly to the extension method with UseForwardedHeaders, the default headers to forward are ForwardedHeaders.None. 必须为 ForwardedHeaders 属性配置要转接的标头。The ForwardedHeaders property must be configured with the headers to forward.

Nginx 配置Nginx configuration

要转发 X-Forwarded-ForX-Forwarded-Proto 标头,请参阅 使用 Nginx 在 Linux 上托管 ASP.NET CoreTo forward the X-Forwarded-For and X-Forwarded-Proto headers, see 使用 Nginx 在 Linux 上托管 ASP.NET Core. 有关详细信息,请参阅 NGINX:使用转发标头For more information, see NGINX: Using the Forwarded header.

Apache 配置Apache configuration

X-Forwarded-For 将会自动添加(请参阅 Apache 模块 mod_proxy:反向代理请求标头)。X-Forwarded-For is added automatically (see Apache Module mod_proxy: Reverse Proxy Request Headers). 要了解如何转发 X-Forwarded-Proto 标头,请参阅 使用 Apache 在 Linux 上托管 ASP.NET CoreFor information on how to forward the X-Forwarded-Proto header, see 使用 Apache 在 Linux 上托管 ASP.NET Core.

转接头中间件选项Forwarded Headers Middleware options

ForwardedHeadersOptions 控制转接头中间件的行为。ForwardedHeadersOptions control the behavior of the Forwarded Headers Middleware. 以下示例更改了默认值:The following example changes the default values:

  • 将转接头中的条目数量限制为 2Limit the number of entries in the forwarded headers to 2.
  • 添加已知的代理地址 127.0.10.1Add a known proxy address of 127.0.10.1.
  • 将转接头名称从默认的 X-Forwarded-For 更改为 X-Forwarded-For-My-Custom-Header-NameChange the forwarded header name from the default X-Forwarded-For to 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";
});
选项Option 说明Description
AllowedHosts 通过 X-Forwarded-Host 标头将主机限制为提供的值。Restricts hosts by the X-Forwarded-Host header to the values provided.
  • 使用 ordinal-ignore-case 比较值。Values are compared using ordinal-ignore-case.
  • 必须排除端口号。Port numbers must be excluded.
  • 如果列表为空,则允许使用所有主机。If the list is empty, all hosts are allowed.
  • 顶级通配符 * 允许所有非空主机。A top-level wildcard * allows all non-empty hosts.
  • 子域通配符允许使用,但不匹配根域。Subdomain wildcards are permitted but don't match the root domain. 例如,*.contoso.com 匹配子域 foo.contoso.com,但不匹配根域 contoso.comFor example, *.contoso.com matches the subdomain foo.contoso.com but not the root domain contoso.com.
  • 允许使用 Unicode 主机名,但应转换为 Punycode 进行匹配。Unicode host names are allowed but are converted to Punycode for matching.
  • IPv6 地址必须包括边界方括号,而且必须位于常规窗体(例如,[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789])中。IPv6 addresses must include bounding brackets and be in conventional form (for example, [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]). IPv6 地址并非专门用于检查不同格式之间的逻辑相等性,也不执行标准化。IPv6 addresses aren't special-cased to check for logical equality between different formats, and no canonicalization is performed.
  • 未能限制允许的主机可能会允许攻击者访问该服务生成的欺骗链接。Failure to restrict the allowed hosts may allow an attacker to spoof links generated by the service.
默认值为空的 IList<string>The default value is an empty IList<string>.
ForwardedForHeaderName 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedForHeaderName 指定的标头。Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedForHeaderName. 如果代理/转发器不使用 X-Forwarded-For 标头,但使用一些其他标头来转发信息,则使用此选项。This option is used when the proxy/forwarder doesn't use the X-Forwarded-For header but uses some other header to forward the information.

默认值为 X-Forwarded-ForThe default is X-Forwarded-For.
ForwardedHeaders 标识应处理的转发器。Identifies which forwarders should be processed. 对于应用的字段的列表,请参阅 ForwardedHeaders 枚举See the ForwardedHeaders Enum for the list of fields that apply. 分配给此属性的典型值为 ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProtoTypical values assigned to this property are ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto.

默认值是 ForwardedHeaders.NoneThe default value is ForwardedHeaders.None.
ForwardedHostHeaderName 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedHostHeaderName 指定的标头。Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedHostHeaderName. 如果代理/转发器不使用 X-Forwarded-Host 标头,但使用一些其他标头来转发信息,则使用此选项。This option is used when the proxy/forwarder doesn't use the X-Forwarded-Host header but uses some other header to forward the information.

默认值为 X-Forwarded-HostThe default is X-Forwarded-Host.
ForwardedProtoHeaderName 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XForwardedProtoHeaderName 指定的标头。Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedProtoHeaderName. 如果代理/转发器不使用 X-Forwarded-Proto 标头,但使用一些其他标头来转发信息,则使用此选项。This option is used when the proxy/forwarder doesn't use the X-Forwarded-Proto header but uses some other header to forward the information.

默认值为 X-Forwarded-ProtoThe default is X-Forwarded-Proto.
ForwardLimit 限制所处理的标头中的条目数。Limits the number of entries in the headers that are processed. 设置为 null 以禁用此限制,但仅应在已配置 KnownProxiesKnownNetworks 的情况下执行此操作。Set to null to disable the limit, but this should only be done if KnownProxies or KnownNetworks are configured. 设置非 null 值是一种预防措施(但不是保证),防止错误配置的代理和恶意请求从网络的侧通道到达。Setting a non-null value is a precaution (but not a guarantee) to guard against misconfigured proxies and malicious requests arriving from side-channels on the network.

转接头中间件按照从右向左的相反顺序处理标头。Forwarded Headers Middleware processes headers in reverse order from right to left. 如果使用默认值 (1),则只会处理标头最右侧的值,除非增加 ForwardLimit 的值。If the default value (1) is used, only the rightmost value from the headers is processed unless the value of ForwardLimit is increased.

默认值为 1The default is 1.
KnownNetworks 从中接受转接头的已知网络的地址范围。Address ranges of known networks to accept forwarded headers from. 使用无类别域际路由选择 (CIDR) 表示法提供 IP 范围。Provide IP ranges using Classless Interdomain Routing (CIDR) notation.

如果服务器使用双模式套接字,则采用 IPv6 格式提供 IPv4 地址(例如,IPv4 格式的 10.0.0.1 以 IPv6 格式表示为 ::ffff:10.0.0.1)。If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format (for example, 10.0.0.1 in IPv4 represented in IPv6 as ::ffff:10.0.0.1). 请参阅 IPAddress.MapToIPv6See IPAddress.MapToIPv6. 通过查看 HttpContext.Connection.RemoteIpAddress 来确定是否需要采用此格式。Determine if this format is required by looking at the HttpContext.Connection.RemoteIpAddress. 有关详细信息,请参阅对表示为 IPv6 地址的 IPv4 地址进行配置部分。For more information, see the Configuration for an IPv4 address represented as an IPv6 address section.

默认值是 IList<IPNetwork>,其中包含 IPAddress.Loopback 的单个条目。The default is an IList<IPNetwork> containing a single entry for IPAddress.Loopback.
KnownProxies 从中接受转接头的已知代理的地址。Addresses of known proxies to accept forwarded headers from. 使用 KnownProxies 指定精确的 IP 地址匹配。Use KnownProxies to specify exact IP address matches.

如果服务器使用双模式套接字,则采用 IPv6 格式提供 IPv4 地址(例如,IPv4 格式的 10.0.0.1 以 IPv6 格式表示为 ::ffff:10.0.0.1)。If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format (for example, 10.0.0.1 in IPv4 represented in IPv6 as ::ffff:10.0.0.1). 请参阅 IPAddress.MapToIPv6See IPAddress.MapToIPv6. 通过查看 HttpContext.Connection.RemoteIpAddress 来确定是否需要采用此格式。Determine if this format is required by looking at the HttpContext.Connection.RemoteIpAddress. 有关详细信息,请参阅对表示为 IPv6 地址的 IPv4 地址进行配置部分。For more information, see the Configuration for an IPv4 address represented as an IPv6 address section.

默认值是 IList<IPAddress>,其中包含 IPAddress.IPv6Loopback 的单个条目。The default is an IList<IPAddress> containing a single entry for IPAddress.IPv6Loopback.
OriginalForHeaderName 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XOriginalForHeaderName 指定的标头。Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalForHeaderName.

默认值为 X-Original-ForThe default is X-Original-For.
OriginalHostHeaderName 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XOriginalHostHeaderName 指定的标头。Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalHostHeaderName.

默认值为 X-Original-HostThe default is X-Original-Host.
OriginalProtoHeaderName 使用由此属性指定的标头,而不是由 ForwardedHeadersDefaults.XOriginalProtoHeaderName 指定的标头。Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalProtoHeaderName.

默认值为 X-Original-ProtoThe default is X-Original-Proto.
RequireHeaderSymmetry 要求正在处理的 ForwardedHeadersOptions.ForwardedHeaders 之间的标头值数量保持同步。Require the number of header values to be in sync between the ForwardedHeadersOptions.ForwardedHeaders being processed.

ASP.NET Core 1.x 默认值是 trueThe default in ASP.NET Core 1.x is true. ASP.NET Core 2.0 默认值是 falseThe default in ASP.NET Core 2.0 or later is false.

方案与使用案例Scenarios and use cases

无法添加转接头并且所有请求都安全的情况When it isn't possible to add forwarded headers and all requests are secure

在某些情况下,可能无法将转接头添加到代理到应用的请求中。In some cases, it might not be possible to add forwarded headers to the requests proxied to the app. 如果代理强制将所有公共外部请求执行为 HTTPS,则在使用任何类型的中间件之前,可以在 Startup.Configure 中手动设置该方案:If the proxy is enforcing that all public external requests are HTTPS, the scheme can be manually set in Startup.Configure before using any type of middleware:

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

此代码可以通过环境变量或者开发环境或过渡环境中的其他配置设置来禁用。This code can be disabled with an environment variable or other configuration setting in a development or staging environment.

处理改变请求路径的基路径和代理Deal with path base and proxies that change the request path

一些代理可完整地传递路径,但是会删除应用基路径以便路由可正常工作。Some proxies pass the path intact but with an app base path that should be removed so that routing works properly. UsePathBaseExtensions.UsePathBase 中间件将路径拆分为 HttpRequest.Path,将应用基路径拆分为 HttpRequest.PathBaseUsePathBaseExtensions.UsePathBase middleware splits the path into HttpRequest.Path and the app base path into HttpRequest.PathBase.

如果 /foo 是作为 /foo/api/1 传递的代理路径的应用基路径,则中间件使用以下命令将 Request.PathBase 设置为 /foo,将 Request.Path 设置为 /api/1If /foo is the app base path for a proxy path passed as /foo/api/1, the middleware sets Request.PathBase to /foo and Request.Path to /api/1 with the following command:

app.UsePathBase("/foo");

当再次反向调用中间件时,将重新应用原始路径和基路径。The original path and path base are reapplied when the middleware is called again in reverse. 要详细了解中间件处理顺序,请参阅 ASP.NET Core 中间件For more information on middleware order processing, see ASP.NET Core 中间件.

如果代理剪裁路径(例如,将 /foo/api/1 转接到 /api/1),请通过设置请求的 PathBase 属性来修复重定向和链接:If the proxy trims the path (for example, forwarding /foo/api/1 to /api/1), fix redirects and links by setting the request's PathBase property:

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

如果代理要添加路径数据,请使用 StartsWithSegments 并分配给 Path 属性,从而放弃部分路径以修复重定向和链接:If the proxy is adding path data, discard part of the path to fix redirects and links by using StartsWithSegments and assigning to the Path property:

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

    return next();
});

配置使用不同标头名称的代理Configuration for a proxy that uses different header names

如果代理不使用名为 X-Forwarded-ForX-Forwarded-Proto 的标头来转发代理地址/端口和原始架构信息,则设置 ForwardedForHeaderNameForwardedProtoHeaderName 选项,使其与代理所用的标头名称相匹配:If the proxy doesn't use headers named X-Forwarded-For and X-Forwarded-Proto to forward the proxy address/port and originating scheme information, set the ForwardedForHeaderName and ForwardedProtoHeaderName options to match the header names used by the 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";
});

对表示为 IPv6 地址的 IPv4 地址进行配置Configuration for an IPv4 address represented as an IPv6 address

如果服务器使用双模式套接字,则采用 IPv6 格式提供 IPv4 地址(例如,IPv4 格式的 10.0.0.1 以 IPv6 格式表示为 ::ffff:10.0.0.1::ffff:a00:1)。If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format (for example, 10.0.0.1 in IPv4 represented in IPv6 as ::ffff:10.0.0.1 or ::ffff:a00:1). 请参阅 IPAddress.MapToIPv6See IPAddress.MapToIPv6. 通过查看 HttpContext.Connection.RemoteIpAddress 来确定是否需要采用此格式。Determine if this format is required by looking at the HttpContext.Connection.RemoteIpAddress.

在以下示例中,提供转接头的网络地址以 IPv6 格式添加到 KnownNetworks 列表中。In the following example, a network address that supplies forwarded headers is added to the KnownNetworks list in IPv6 format.

IPv4 地址:10.11.12.1/8IPv4 address: 10.11.12.1/8

转换后的 IPv6 地址:::ffff:10.11.12.1Converted IPv6 address: ::ffff:10.11.12.1
转换后的前缀长度:104Converted prefix length: 104

也可以提供十六进制格式的地址(10.11.12.1 以 IPv6 格式表示为 ::ffff:0a0b:0c01)。You can also supply the address in hexadecimal format (10.11.12.1 represented in IPv6 as ::ffff:0a0b:0c01). 将 IPv4 地址转换为 IPv6 时,将 96 添加到 CIDR 前缀长度(示例中的 8)以说明其他 ::ffff: IPv6 前缀 (8 + 96 = 104)。When converting an IPv4 address to IPv6, add 96 to the CIDR Prefix Length (8 in the example) to account for the additional ::ffff: IPv6 prefix (8 + 96 = 104).

// To access IPNetwork and IPAddress, add the following namespaces:
// using using System.Net;
// using Microsoft.AspNetCore.HttpOverrides;
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Add(new IPNetwork(
        IPAddress.Parse("::ffff:10.11.12.1"), 104));
});

转发 Linux 和非 IIS 反向代理的方案Forward the scheme for Linux and non-IIS reverse proxies

如果将站点部署到 Azure Linux 应用服务、Azure Linux 虚拟机 (VM),或者部署到除 IIS 之外的任何其他反向代理之后,调用 UseHttpsRedirectionUseHsts 的应用都会使站点进入无限循环。Apps that call UseHttpsRedirection and UseHsts put a site into an infinite loop if deployed to an Azure Linux App Service, Azure Linux virtual machine (VM), or behind any other reverse proxy besides IIS. 反向代理终止 TLS,并且 Kestrel 未发现正确的请求方案。TLS is terminated by the reverse proxy, and Kestrel isn't made aware of the correct request scheme. 由于 OAuth 和 OIDC 生成了错误的重定向,因此它们在此配置中也会出现故障。OAuth and OIDC also fail in this configuration because they generate incorrect redirects. UseIISIntegration 在 IIS 之后运行时会添加和配置转接头中间件,但 Linux(Apache 或 Nginx 集成)没有匹配的自动配置。UseIISIntegration adds and configures Forwarded Headers Middleware when running behind IIS, but there's no matching automatic configuration for Linux (Apache or Nginx integration).

要从非 IIS 方案中的代理中转发方案,请添加并配置转接头中间件。To forward the scheme from the proxy in non-IIS scenarios, add and configure Forwarded Headers Middleware. Startup.ConfigureServices 中,使用以下代码:In Startup.ConfigureServices, use the following code:

// 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();
    });
}

疑难解答Troubleshoot

如果未按预期转接标头,请启用日志记录When headers aren't forwarded as expected, enable logging. 如果日志没有提供足够的信息来解决问题,请枚举服务器收到的请求标头。If the logs don't provide sufficient information to troubleshoot the problem, enumerate the request headers received by the server. 使用内联中间件将请求标头写入应用程序响应或记录标头。Use inline middleware to write request headers to an app response or log the headers.

要将标头写入应用的响应,请在 Startup.Configure 中调用 UseForwardedHeaders 后立即放置以下终端内联中间件:To write the headers to the app's response, place the following terminal inline middleware immediately after the call to UseForwardedHeaders in Startup.Configure:

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}");
});

可以写入日志,而不是响应正文。You can write to logs instead of the response body. 借助写入日志,站点可在调试时正常运行。Writing to logs allows the site to function normally while debugging.

要写入日志而不是响应正文,请执行以下操作:To write logs rather than to the response body:

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: {REMOTE_IP_ADDRESS}", 
        context.Connection.RemoteIpAddress);

    await next();
});

处理时,X-Forwarded-{For|Proto|Host} 值将移至 X-Original-{For|Proto|Host}When processed, X-Forwarded-{For|Proto|Host} values are moved to X-Original-{For|Proto|Host}. 如果给定标头中有多个值,则转接头中间件按照从右向左的相反顺序处理标头。If there are multiple values in a given header, Forwarded Headers Middleware processes headers in reverse order from right to left. 默认 ForwardLimit1(一),因此只会处理标头最右侧的值,除非增加 ForwardLimit 的值。The default ForwardLimit is 1 (one), so only the rightmost value from the headers is processed unless the value of ForwardLimit is increased.

在处理转接头之前,请求的原始远程 IP 必须与 KnownProxiesKnownNetworks 列表中的条目匹配。The request's original remote IP must match an entry in the KnownProxies or KnownNetworks lists before forwarded headers are processed. 这通过不接受来自不受信任的代理的转发器来限制标头欺骗。This limits header spoofing by not accepting forwarders from untrusted proxies. 检测到未知代理时,日志记录会指出代理的地址:When an unknown proxy is detected, logging indicates the address of the proxy:

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

在上述示例中,10.0.0.100 是代理服务器。In the preceding example, 10.0.0.100 is a proxy server. 如果该服务器是受信任的代理,请将服务器的 IP 地址添加到 Startup.ConfigureServices 中的 KnownProxies(或将受信任的网络添加到 KnownNetworks)。If the server is a trusted proxy, add the server's IP address to KnownProxies (or add a trusted network to KnownNetworks) in Startup.ConfigureServices. 有关详细信息,请参阅转接头中间件选项部分。For more information, see the Forwarded Headers Middleware options section.

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

重要

仅允许受信任的代理和网络转接头。Only allow trusted proxies and networks to forward headers. 否则,可能会受到 IP 欺骗攻击。Otherwise, IP spoofing attacks are possible.

转发证书Certificate forwarding

在 Azure 上On Azure

请参阅 Azure 文档以配置 Azure Web 应用。See the Azure documentation to configure Azure Web Apps. 在应用的 Startup.Configure 方法中,在调用 app.UseAuthentication(); 前添加以下代码:In your app's Startup.Configure method, add the following code before the call to app.UseAuthentication();:

app.UseCertificateForwarding();

此外,还需要配置证书转发中间件,以指定 Azure 使用的标头名称。You'll also need to configure the Certificate Forwarding middleware to specify the header name that Azure uses. 在应用的 Startup.ConfigureServices 方法中,添加以下代码来配置中间件从中生成证书的标头:In your app's Startup.ConfigureServices method, add the following code to configure the header from which the middleware builds a certificate:

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

使用其他 Web 代理With other web proxies

如果使用的代理不是 IIS 或 Azure 的 Web 应用应用程序请求路由,请配置代理,以便转发其在 HTTP 标头中收到的证书。If you're using a proxy that isn't IIS or Azure's Web Apps Application Request Routing, configure your proxy to forward the certificate it received in an HTTP header. 在应用的 Startup.Configure 方法中,在调用 app.UseAuthentication(); 前添加以下代码:In your app's Startup.Configure method, add the following code before the call to app.UseAuthentication();:

app.UseCertificateForwarding();

此外,还需要配置证书转发中间件,以指定标头名称。You'll also need to configure the Certificate Forwarding middleware to specify the header name. 在应用的 Startup.ConfigureServices 方法中,添加以下代码来配置中间件从中生成证书的标头:In your app's Startup.ConfigureServices method, add the following code to configure the header from which the middleware builds a certificate:

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

最后,如果代理正在执行除对证书进行 base64 编码之外的操作(与 Nginx 的情况一样),请设置 HeaderConverter 选项。Finally, if the proxy is doing something other than base64 encoding the certificate (as is the case with Nginx), set the HeaderConverter option. 请看下面 Startup.ConfigureServices 中的示例:Consider the following example in 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;
    }
});

其他资源Additional resources