配置ASP.NET Core使用反向代理

image

当请求经过代理服务器、负载均衡器中转到达应用服务器时,请求的scheme(HTTP or HTTPS)、Host、端口以及原始客户端 IP 地址已经经过改变,原始信息在标头中转接,这些信息在一些请求处理中很重要,例如在重定向、身份验证、链接生成、策略评估和客户端地理位置中。

ForwardedHeadersMiddleware中间件将读取包含原始请求信息的标头并填充 HttpContext 上的关联字段来恢复正确的原始请求信息,以便后续的中间件像处理普通请求一样处理经过转发的请求。

代理转接 HTTP 标头主要有以下几种:

标题 说明
X-Forwarded-For (XFF) 包含原始客户端和后续代理的 IP 地址以及可选端口号。如果请求经过多个代理,则该值将是列表。第一个参数表示最初发出请求的客户端,后续代理按顺序排列, 链中的最后一个代理不在参数列表中。
X-Forwarded-Proto (XFP) 指示原始请求是 HTTP 还是 HTTPS。 如果请求经过多个代理,则该值将是列表,同上。
X-Forwarded-Host (XFH) 包含客户端请求的原始主机。如果请求经过多个代理,则该值将是列表,同上。。
X-Forwarded-Prefix 客户端请求的原始基路径。 此标头对于应用服务器正确生成 URL、重定向或回到客户端的链接十分有用。如果请求经过多个代理,则该值将是列表,同上。。

ForwardedHeadersMiddleware中间件将更新以下值:

属性 说明
HttpContext.Connection.RemoteIpAddress 更新为 X-Forwarded-For 标头值。使用的值将从 X-Forwarded-For 中删除,HttpContext.Connection.RemoteIpAddress 的旧值将保存在 X-Original-For 中。如果X-Forwarded-For是代理链,将重复以上过程,在X-Original-For构建一个反向代理链。
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 中。如果是代理链将重复以上过程。

添加ForwardedHeadersMiddleware中间件

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

// 配置ForwardedHeaders
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
	// 或设置为 ForwardedHeaders.All 以便处理所有转发标头
});

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

重要:ForwardedHeadersMiddleware必须在调用 UseHsts 之前运行。

配置ForwardedHeadersOptions

需要配置ForwardedHeadersOptions.ForwardedHeaders属性,指定需要转发的标头。如果未配置,则默认值是ForwardedHeaders.None,将不会用转发标头更新任何属性,即ForwardedHeadersMiddleware不起任何作用。

KnownNetworksKnownProxies的默认值为环回地址,即默认只响应回环地址代理。

默认转发标头名称为 X-Forwarded-ForX-Forwarded-ProtoX-Forwarded-HostX-Forwarded-Prefix

处理基路径

一些代理服务器可能会加上基路径,例如应用服务器路径是/api/1,而代理服务器的路径是/foo/api/1,加上了基路径/foo。通常代理转发到应用服务器时会移除基路径,并在X-Forwarded-Prefix标头中添加基路径。如前所述,ForwardedHeadersMiddleware中间件会将Request.PathBase设置为基路径/foo

如果代理服务器没有移除基路径,并且也没有使用X-Forwarded-Prefix标头,这种情况需要使用UsePathBase中间件将Request.PathBase 设置为 /foo,将 Request.Path 改为 /api/1

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

UsePathBase必须在app.UseRouting之前调用,以便路由中间匹配正确的路由。当再次反向调用中间件时,将重新应用原始路径和基路径。

如果只是移除基路径,示例:

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

    return next(context);
});

转发客户端证书

为使用了双向TLS认证的Azure 应用服务器配置证书转发。

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

要在调用UseAuthentication之前调用UseCertificateForwarding

故障排查

如果存在故障,调用UseHttpLogging中间件打印Http日志,观察经过ForwardedHeaders中间件后的请求信息。

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

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

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

app.UseAuthorization();

app.MapRazorPages();

app.Run();

必须在UseForwardedHeaders之后调用UseHttpLogging

Http日志以Information级别显示。如果未显示,请将 "Microsoft.AspNetCore.HttpLogging": "Information" 添加到 appsettings.Development.json 文件:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.HttpLogging": "Information"
    }
  }
}
posted @ 2025-11-15 20:39  星墨  阅读(19)  评论(0)    收藏  举报