.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
相关产品推荐
相关产品推荐

