You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让中间件在ASP.NET Core Blazor的AuthorizeAttribute前执行?

问题:Blazor InteractiveServer [Authorize]触发Discord OAuth时redirect_uri使用HTTP(负载均衡后)

问题场景

我有一个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 06:32:10