如何让中间件在ASP.NET Core Blazor的AuthorizeAttribute前执行?
问题场景
我有一个Blazor InteractiveServer页面,使用[Authorize]属性触发Discord OAuth2授权流程:
@page "/mypage" @using Microsoft.AspNetCore.Authorization @attribute [Authorize] @rendermode InteractiveServer
站点部署在TLS终止负载均衡器后,OAuth2授权重定向生成的redirect_uri始终使用http协议。常规的解决方案(负载均衡配置X-Forwarded-Proto头,Web应用中使用UseForwardedHeaders)对该场景无效——访问带[Authorize]的页面时,主中间件管道完全未执行,授权挑战在管道运行前就触发了。
相关配置代码:
var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddConfiguration(config); builder.Services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); builder.Services .AddAuthentication(DiscordOptions.AuthenticationScheme) .AddCookie() .AddDiscord(x => { x.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; x.ClientId = config.GetValue<string>("Discord:ClientId")!; x.ClientSecret = config.GetValue<string>("Discord:ClientSecret")!; var scopes = config.GetSection("Discord:Scopes").Get<string[]>(); if (scopes is not null) x.WithClaims(scopes); var prompt = config.GetValue<string>("Discord:Prompt"); if (prompt is not null) x.WithPrompt(prompt); }); var app = builder.Build(); // 访问带[Authorize]的页面时,此中间件不会触发 app.Use((ctx, next) => { Debugger.Break(); return next(); }); app.UseForwardedHeaders(); if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseAntiforgery(); app.UseStaticFiles(); app.MapControllers(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); await app.Services.GetService<IdentityDbContext>()!.Database.MigrateAsync(); app.Run();
已验证:
- 无
[Authorize]属性的页面能正常通过UseForwardedHeaders修改Scheme - 手动添加
UseAuthentication/UseAuthorization无效果 - 授权挑战使用独立的筛选器管道,而非主中间件管道
原因分析
Blazor InteractiveServer的[Authorize]属性是作为端点筛选器执行的,它的运行时机早于主中间件管道。当访问受保护页面时,授权筛选器会直接触发OAuth2挑战,此时UseForwardedHeaders还未执行,HttpContext.Request.Scheme仍为负载均衡转发的http,导致生成的redirect_uri协议错误。
解决方案
方案1:在Discord OAuth事件中修正RedirectUri
通过Discord OAuth的事件钩子,手动从X-Forwarded-Proto头获取正确的协议,修正重定向地址:
builder.Services.PostConfigure<DiscordOptions>(DiscordOptions.AuthenticationScheme, options => { options.Events = new OAuthEvents { OnRedirectToAuthorizationEndpoint = context => { // 读取负载均衡传递的X-Forwarded-Proto头 if (context.Request.Headers.TryGetValue("X-Forwarded-Proto", out var protoHeader)) { var originalUri = new Uri(context.RedirectUri); // 构建修正后的HTTPS地址 var correctedUri = new UriBuilder(originalUri) { Scheme = protoHeader, Port = -1 // 自动使用对应协议的默认端口(HTTPS=443) }.Uri; context.RedirectUri = correctedUri.ToString(); } context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; } }; });
方案2:在Blazor端点筛选器前应用ForwardedHeaders
给Blazor的路由端点添加前置筛选器,提前执行ForwardedHeaders逻辑,确保Scheme在授权挑战前被修正:
app.MapRazorComponents<App>() .AddInteractiveServerRenderMode() .AddEndpointFilter(async (context, next) => { // 获取ForwardedHeaders配置并执行中间件逻辑 var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<ForwardedHeadersOptions>>().Value; var middleware = new ForwardedHeadersMiddleware(_ => next(context), options); await middleware.Invoke(context.HttpContext); return await next(context); });
方案3:全局配置认证事件修正Scheme
通过认证全局事件,在任何授权挑战前修正请求的Scheme:
builder.Services.Configure<AuthenticationOptions>(options => { options.Events = new AuthenticationEvents { OnRedirectToLogin = context => { if (context.Request.Headers.TryGetValue("X-Forwarded-Proto", out var proto)) { context.Request.Scheme = proto.ToString(); } return Task.CompletedTask; } }; });
内容的提问来源于stack exchange,提问作者Kana Ki
相关产品推荐
相关产品推荐

