Blazor自定义AuthenticationStateProvider未设置Cookie及页面授权失败问题
核心问题梳理
你的配置存在几个关键问题:Cookie策略冲突、认证状态未通知更新、HttpClient未配置Cookie传递、登录接口冗余特性、Cookie验证逻辑错误,这些共同导致浏览器无法创建Cookie,受保护页面无法识别已认证用户。
具体修复步骤
1. 统一Cookie SameSite策略
Server项目Program.cs中,AddCookie配置的SameSite是Lax,但后续UseCookiePolicy设置的是Strict,两者冲突会导致Cookie无法正常写入。移除单独的CookiePolicy配置,或者将其SameSite改为Lax:
// 删掉这段单独的CookiePolicy配置 // var cookiePolicyOptions = new CookiePolicyOptions // { // MinimumSameSitePolicy = SameSiteMode.Strict, // }; // app.UseCookiePolicy(cookiePolicyOptions); // 或者修改为和AddCookie一致的Lax var cookiePolicyOptions = new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax, }; app.UseCookiePolicy(cookiePolicyOptions);
2. 配置WASM端HttpClient传递Cookie
Client项目Program.cs中,默认HttpClient不会自动携带Cookie,需要添加配置确保认证Cookie能随请求发送:
var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.Logging.SetMinimumLevel(LogLevel.Debug); // 添加AuthorizationMessageHandler,确保HttpClient携带认证Cookie builder.Services.AddScoped<AuthorizationMessageHandler>(); builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler<AuthorizationMessageHandler>(); // 注册其他服务 builder.Services.AddSingleton<IUserSettingsService, UserSettingsService>(); builder.Services.AddScoped<ToastService>(); builder.Services.AddScoped<ModalService>(); await builder.Build().RunAsync();
同时在Login.razor中注入命名HttpClient:
@inject IHttpClientFactory HttpClientFactory // ... private async Task HandleLogin() { var httpClient = HttpClientFactory.CreateClient("ServerAPI"); var response = await httpClient.PostAsJsonAsync("/api/auth/login", loginModel); // ... }
3. 让AuthenticationStateProvider主动通知状态变化
当前的CustomAuthenticationStateProvider在登录成功后不会通知Blazor更新认证状态,导致UI无法感知用户已登录。修改CustomAuthenticationStateProvider:
namespace Dragon.Authentications { public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly IHttpContextAccessor _httpContextAccessor; public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public override Task<AuthenticationState> GetAuthenticationStateAsync() { var httpContext = _httpContextAccessor.HttpContext; var user = httpContext?.User; if (user?.Identity != null && user.Identity.IsAuthenticated) { return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(user))); } return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); } // 添加状态通知方法 public void NotifyAuthenticationStateChanged() { NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } } }
然后在Login.razor的登录成功逻辑中调用这个方法:
if (response.IsSuccessStatusCode) { ((CustomAuthenticationStateProvider)AuthenticationStateProvider).NotifyAuthenticationStateChanged(); // 强制刷新页面,确保Cookie生效 Navigation.NavigateTo("/", forceLoad: true); }
4. 移除登录接口的冗余特性
AuthController的Login方法同时标记了[Authorize]和[AllowAnonymous],虽然[AllowAnonymous]会覆盖,但冗余配置可能导致混淆,直接删掉[Authorize]:
[HttpPost("login")] [AllowAnonymous] // 只保留这个 public async Task<IActionResult> Login([FromBody] LoginModel request)
5. 修复Cookie验证逻辑的错误
OnValidatePrincipal中,若用户已认证但没有过期Claim,当前逻辑会直接拒绝用户,这是错误的。修改验证逻辑:
o.Events = new CookieAuthenticationEvents { OnValidatePrincipal = ctx => { if (ctx.Principal?.Identity?.IsAuthenticated ?? false) { var expirationClaim = ctx.Principal.FindFirst(ClaimTypes.Expiration)?.Value; // 只有当存在过期Claim且已过期时,才拒绝用户 if (!string.IsNullOrEmpty(expirationClaim) && DateTimeOffset.TryParse(expirationClaim, out var expiration) && expiration < DateTimeOffset.UtcNow) { ctx.RejectPrincipal(); return ctx.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } } // 未认证的情况交给默认处理,不要主动Reject return Task.CompletedTask; } };
6. 调整中间件顺序
Server项目Program.cs中,中间件顺序必须严格遵循:静态文件 → 路由 → Cookie策略 → 认证 → 授权。当前UseAntiforgery放在了UseCookiePolicy之前,调整顺序:
app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseCookiePolicy(); // 移到这里,在认证之前 app.UseAntiforgery(); app.UseAuthentication(); app.UseAuthorization();
7. 确保Login页面的RenderMode正确
Login.razor使用的InteractiveAutoRenderMode(false)可能导致WASM端处理请求,无法正确设置Cookie。改为Server渲染模式:
@rendermode InteractiveServerRenderMode
内容的提问来源于stack exchange,提问作者Prefetcher Shatnawi

