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

Blazor Server .NET 6.0 Cookie认证多标签页及过期失效问题求助

Blazor Server Cookie认证问题解决方案

一、实现多标签页同步登出

Blazor Server每个标签页对应独立的SignalR连接,需通过客户端广播+服务器端session校验实现同步登出:

1. 客户端监听与标记

在wwwroot/js/site.js中添加工具方法,利用localStorage的storage事件实现跨标签页通信:

window.authUtils = {
    // 设置登出标记
    setLogoutFlag: () => localStorage.setItem('userLoggedOut', Date.now().toString()),
    // 清除标记
    clearLogoutFlag: () => localStorage.removeItem('userLoggedOut'),
    // 初始化登出监听器
    initLogoutListener: (callback) => {
        window.addEventListener('storage', (event) => {
            if (event.key === 'userLoggedOut') callback();
        });
    }
};

2. 自定义认证状态提供器集成监听

修改CustomAuthenticationStateProvider,注入IJSRuntime并初始化监听器,收到登出通知时触发状态变更:

public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
    private readonly IServiceScopeFactory _scopeFactory;      
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly TimeSpanSettings _timeSpanSettings;
    private readonly IJSRuntime _jsRuntime;

    public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor,
                                             IServiceScopeFactory scopeFactory,
                                             ILoggerFactory loggerFactory,
                                             IOptions<TimeSpanSettings> timeSpanSettingsOptions,
                                             IJSRuntime jsRuntime) 
        : base(loggerFactory)
    {
        _scopeFactory = scopeFactory;
        _httpContextAccessor = httpContextAccessor;
        _timeSpanSettings = timeSpanSettingsOptions.Value;
        _jsRuntime = jsRuntime;
        _ = InitLogoutListener();
    }

    private async Task InitLogoutListener()
    {
        await _jsRuntime.InvokeVoidAsync("authUtils.initLogoutListener", 
            DotNetObjectReference.Create(this));
    }

    [JSInvokable]
    public async Task OnLogoutDetected()
    {
        // 触发认证状态更新
        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }

    // 保留其他原有代码...
}

3. 登出流程改造

在登出组件中,先执行服务器端登出,再设置客户端标记:

@inject IHttpContextAccessor HttpContextAccessor
@inject IJSRuntime JsRuntime
@inject NavigationManager NavManager

private async Task HandleLogout()
{
    // 服务器端注销认证
    await HttpContextAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    // 设置跨标签页登出标记
    await JsRuntime.InvokeVoidAsync("authUtils.setLogoutFlag");
    // 跳转到登录页
    NavManager.NavigateTo("/login", forceLoad: true);
}

4. 服务器端Session有效性校验

使用分布式缓存(如IDistributedCache)维护有效Session列表,登出时移除对应SessionId,在重验证时校验:

protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken)
{
    var sessionId = authenticationState.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value;
    
    if (string.IsNullOrEmpty(sessionId) || !authenticationState.User.Identity.IsAuthenticated)
    {
        return false;
    }

    // 从缓存校验Session有效性
    using var scope = _scopeFactory.CreateScope();
    var cache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();
    var validSession = await cache.GetStringAsync($"ValidSession_{sessionId}", cancellationToken);

    // Session不存在则验证失败
    return validSession != null;
}

注意:用户登录时需将SessionId存入缓存,过期时间与Cookie保持一致。

二、Cookie过期后阻止组件加载

核心问题是Blazor Server SignalR连接建立后不会自动重新读取Cookie,需通过定期重验证+全局授权拦截实现:

1. 修复认证状态提供器的GetAuthenticationStateAsync

不要覆盖基类方法(或正确调用基类),确保RevalidatingServerAuthenticationStateProvider的定期重验证逻辑生效:

// 移除原有的GetAuthenticationStateAsync覆盖,或修改为:
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
    var httpContext = _httpContextAccessor.HttpContext;
    if (httpContext != null && httpContext.User.Identity.IsAuthenticated)
    {
        return Task.FromResult(new AuthenticationState(httpContext.User));
    }
    // 委托给基类处理后续SignalR请求的认证状态
    return base.GetAuthenticationStateAsync();
}

2. 启用全局授权策略

在Program.cs中配置全局授权,强制所有页面需认证访问:

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

3. 组件层用AuthorizeView拦截未认证内容

所有需要认证的组件/页面使用AuthorizeView包裹,自动切换未认证视图:

<AuthorizeView>
    <Authorized>
        <!-- 已认证可访问的内容 -->
    </Authorized>
    <NotAuthorized>
        <RedirectToLogin />
    </NotAuthorized>
</AuthorizeView>

4. 监听认证状态变更自动跳转

在布局组件(如MainLayout.razor)中监听认证状态变化,未认证时强制跳转到登录页:

@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager NavManager

@code {
    protected override void OnInitialized()
    {
        AuthStateProvider.StateChanged += async (_, __) =>
        {
            var state = await AuthStateProvider.GetAuthenticationStateAsync();
            if (!state.User.Identity.IsAuthenticated)
            {
                NavManager.NavigateTo("/login", forceLoad: true);
            }
        };
    }
}

关键配置补充

确保Program.cs中正确注册自定义认证状态提供器:

builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddDistributedMemoryCache(); // 开发环境用内存缓存,生产建议用Redis

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:45:04