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

.NET 8 Blazor Web App模板中[Authorize]API未登录跳转登录而非返回401

问题:.NET 8 Blazor WASM API控制器未登录时返回登录页而非401

我正在将.NET WASM项目从.NET 6迁移到.NET 8,使用VS2022带独立账户的模板,交互模式设置为「自动(服务器和WebAssembly)及每页/组件」。模板自带的登录功能正常,但未登录状态下访问带有[Authorize]特性的API控制器时,服务器返回登录页面而非401未授权响应,登录后则一切正常。

我尝试过配置CookieAuthenticationEvents修改未登录跳转逻辑,也参考过相关解决方案,但均无效,推测问题与模板的用户账户配置有关。


相关代码

后端Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
    .AddIdentityCookies();

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<RGDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<RGDbContext>()
    .AddSignInManager()
    .AddDefaultTokenProviders();

string baseURI = builder.Configuration["applicationUrl"];
if (!string.IsNullOrEmpty(baseURI))
{
    builder.Services.AddScoped(sp => new HttpClient
    {
        BaseAddress = new Uri(baseURI)
    });

    // Add a CORS policy for the client
    builder.Services.AddCors(
        options => options.AddDefaultPolicy(
            policy => policy.WithOrigins(baseURI)
                .AllowAnyMethod()
                .AllowAnyHeader()));
}

builder.Services.Configure<CookieAuthenticationEvents>(options =>
{
    options.OnRedirectToLogin = context =>
    {
        if (context.Request.Path.Value.Contains("api"))
        {
            context.Response.Clear();
            context.Response.StatusCode = 401;
            return Task.FromResult(0);
        }
        context.Response.Redirect(context.RedirectUri);
        return Task.FromResult(0);
    };
});

builder.Services.AddControllersWithViews(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true
    );

builder.Services.AddAntiforgery(options =>
{
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

#region  ****My services and configs****
... stuff
#endregion  ***********************************

//////////APP Section
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseCors();
app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(RaffleGames.Client._Imports).Assembly);

app.MapAdditionalIdentityEndpoints();

app.MapControllers();
    
app.Run();

客户端Program.cs

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddSingleton<AuthenticationStateProvider, PersistentAuthenticationStateProvider>();

builder.Services.AddScoped(sp => new HttpClient
{
    BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});

builder.Services.AddScoped<UserService>();

await builder.Build().RunAsync();

TestController(未授权)

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    [HttpGet]
    public async Task<ActionResult<string>> Get()
    {
        await Task.Delay(1000);
        return Ok("Yes");
    }
}

ValuesController(带[Authorize])

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public async Task<ActionResult<string>> Get()
    {
        await Task.Delay(1000);
        return Ok("Yes");
    }
}

解决方案

1. 针对Identity的Application Scheme配置跳转逻辑

模板使用AddIdentityCookies()时,默认的认证Cookie方案是IdentityConstants.ApplicationScheme,直接配置CookieAuthenticationEvents不会生效,需要改用ConfigureApplicationCookie:

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        // 识别API请求,返回401而非跳转登录页
        if (context.Request.Path.StartsWithSegments("/api") 
            && context.Response.StatusCode == StatusCodes.Status200OK)
        {
            context.Response.Clear();
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return Task.CompletedTask;
        }
        // 非API请求正常跳转登录页
        context.Response.Redirect(context.RedirectUri);
        return Task.CompletedTask;
    };
});

2. 调整中间件管道顺序

确保认证和授权中间件在管道中正确执行,在UseAntiforgery()之后、MapRazorComponents()之前添加:

app.UseAuthentication();
app.UseAuthorization();

3. 完善CORS配置(跨域场景)

如果客户端和服务器存在跨域,需要在CORS策略中添加AllowCredentials(),确保认证Cookie能正常传递:

builder.Services.AddCors(options => options.AddDefaultPolicy(policy =>
    policy.WithOrigins(baseURI)
          .AllowAnyMethod()
          .AllowAnyHeader()
          .AllowCredentials()));

4. 验证API请求判定逻辑

使用StartsWithSegments("/api")替代Contains("api"),避免误判包含api字符串的非API路径,同时检查响应状态码确保是正常请求被拦截。


内容的提问来源于stack exchange,提问作者Brett JB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:32:07