.NET 6 Blazor Server中Cookie过期后IsAuthenticated仍返回True的问题
问题根源
Blazor Server基于SignalR建立长连接,默认的ServerAuthenticationStateProvider只会在连接建立时获取一次认证状态并缓存,不会主动检测Cookie是否过期。只有页面刷新(重建SignalR连接)时才会重新读取Cookie状态,导致Cookie过期后IsAuthenticated()仍返回true。
解决方案:自定义AuthenticationStateProvider
通过自定义认证状态提供者,每次获取认证状态时验证Cookie有效性,过期则更新状态并通知组件。
1. 创建自定义AuthenticationStateProvider
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using System.Security.Claims; public class CustomAuthenticationStateProvider : ServerAuthenticationStateProvider { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IServiceScopeFactory _scopeFactory; private readonly CookieAuthenticationOptions _cookieOptions; public CustomAuthenticationStateProvider( IHttpContextAccessor httpContextAccessor, IServiceScopeFactory scopeFactory, IOptions<CookieAuthenticationOptions> cookieOptions) { _httpContextAccessor = httpContextAccessor; _scopeFactory = scopeFactory; _cookieOptions = cookieOptions.Value; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var cachedState = await base.GetAuthenticationStateAsync(); var user = cachedState.User; if (user.Identity.IsAuthenticated) { var httpContext = _httpContextAccessor.HttpContext; if (httpContext != null) { // 获取认证Cookie var cookieValue = httpContext.Request.Cookies[_cookieOptions.Cookie.Name]; if (string.IsNullOrEmpty(cookieValue)) { // Cookie不存在,更新为未认证状态 var unauthenticatedState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); NotifyAuthenticationStateChanged(Task.FromResult(unauthenticatedState)); return unauthenticatedState; } // 解析并验证Cookie有效期 var ticket = await ParseCookieTicket(cookieValue, httpContext); if (ticket == null || ticket.ExpiresUtc <= DateTimeOffset.UtcNow) { // Cookie已过期或无效,更新为未认证状态 var unauthenticatedState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); NotifyAuthenticationStateChanged(Task.FromResult(unauthenticatedState)); return unauthenticatedState; } } } return cachedState; } private async Task<AuthenticationTicket> ParseCookieTicket(string cookieValue, HttpContext httpContext) { using var scope = _scopeFactory.CreateScope(); var dataProtector = scope.ServiceProvider .GetRequiredService<IDataProtectionProvider>() .CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", _cookieOptions.Cookie.Name, "v2"); try { var ticketDataFormat = new TicketDataFormat(dataProtector); return ticketDataFormat.Unprotect(cookieValue); } catch { // 解析失败(Cookie篡改或过期) return null; } } }
2. 注册自定义Provider
在Program.cs中替换默认的AuthenticationStateProvider:
// 先确保已添加IHttpContextAccessor builder.Services.AddHttpContextAccessor(); // 注册自定义认证状态提供者 builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
3. 优化组件中的认证检查
在Razor组件中,除了在OnInitializedAsync调用IsAuthenticated(),还可以订阅认证状态变化事件,实时响应:
@inject IUserInfoService UserInfoService @inject NavigationManager NavManager @implements IDisposable @code { protected override async Task OnInitializedAsync() { // 初始检查 await CheckAuthentication(); // 订阅状态变化(如果UserInfoService实现了事件) if (UserInfoService is UserInfoService serviceWithEvents) { serviceWithEvents.AuthenticationStateChanged += OnAuthStateChanged; } } private async Task CheckAuthentication() { var isAuthenticated = await UserInfoService.IsAuthenticated(); if (!isAuthenticated) { NavManager.NavigateTo("/Identity/Account/Login", forceLoad: true); } } private void OnAuthStateChanged(object sender, EventArgs e) { InvokeAsync(CheckAuthentication); } public void Dispose() { if (UserInfoService is UserInfoService serviceWithEvents) { serviceWithEvents.AuthenticationStateChanged -= OnAuthStateChanged; } } }
补充:优化UserInfoService(可选)
如果需要主动触发状态检查,可以给UserInfoService添加定时检查和事件通知:
public class UserInfoService : IUserInfoService, IDisposable { private readonly AuthenticationStateProvider _authProvider; private Timer _checkTimer; private bool _lastAuthState; public UserInfoService(AuthenticationStateProvider authProvider) { _authProvider = authProvider; _ = InitializeAuthStateAsync(); // 每分钟检查一次认证状态 _checkTimer = new Timer(async _ => await RefreshAuthStateAsync(), null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); } private async Task InitializeAuthStateAsync() { _lastAuthState = await IsAuthenticated(); } private async Task RefreshAuthStateAsync() { var currentState = await IsAuthenticated(); if (currentState != _lastAuthState) { _lastAuthState = currentState; AuthenticationStateChanged?.Invoke(this, EventArgs.Empty); } } public async Task<bool> IsAuthenticated() { var authState = await _authProvider.GetAuthenticationStateAsync(); return authState.User.Identity.IsAuthenticated; } public event EventHandler AuthenticationStateChanged; public void Dispose() { _checkTimer?.Dispose(); } }
关键说明
- 自定义Provider会在每次调用
GetAuthenticationStateAsync时验证Cookie,确保状态实时性。 NotifyAuthenticationStateChanged方法会通知所有依赖认证状态的组件重新渲染,无需手动刷新页面。- 解析Cookie时使用
IServiceScopeFactory避免HttpContext生命周期冲突,确保在Blazor Server的长连接中正常工作。
内容的提问来源于stack exchange,提问作者mnu-nasir
相关产品推荐
相关产品推荐

