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

.NET 6 Blazor Server中Cookie过期后IsAuthenticated仍返回True的问题

解决Blazor Server Cookie过期后认证状态不更新的问题

问题根源

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 08:24:20