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

Blazor WASM组件与控制器认证授权状态不一致问题求助

Blazor WASM 客户端认证状态与服务端Cookie同步实现思路

核心思路

让客户端的CustomAuthenticationStateProvider不再仅依赖LocalStorage缓存,而是通过调用服务端接口校验Cookie有效性,以此同步两端的认证状态。


1. 服务端新增会话校验接口

在服务端添加一个无需授权的接口,用来验证当前请求的Cookie是否有效,并返回用户基础信息:

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    [HttpGet("check-session")]
    public IActionResult CheckSession()
    {
        if (User.Identity?.IsAuthenticated == true)
        {
            return Ok(new 
            { 
                IsAuthenticated = true, 
                UserName = User.Identity.Name,
                // 按需添加其他必要字段,比如角色、权限等
            });
        }
        return Ok(new { IsAuthenticated = false });
    }
}

2. 修改CustomAuthenticationStateProvider实现

重写认证状态获取逻辑,优先调用服务端校验接口,同步更新客户端缓存与认证状态:

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using Blazored.LocalStorage;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;
    private readonly ILocalStorageService _localStorage;
    private readonly AuthenticationState _anonymousState = new(new ClaimsPrincipal(new ClaimsIdentity()));

    public CustomAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
    {
        _httpClient = httpClient;
        _localStorage = localStorage;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        try
        {
            // 调用服务端会话校验接口
            var sessionResult = await _httpClient.GetFromJsonAsync<SessionValidationResult>("api/auth/check-session");

            if (sessionResult?.IsAuthenticated == true)
            {
                // 构造已认证的ClaimsPrincipal
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, sessionResult.UserName)
                    // 可根据返回结果添加角色、权限等Claim
                };
                var identity = new ClaimsIdentity(claims, "CookieAuthentication");
                var authenticatedUser = new ClaimsPrincipal(identity);

                // 同步更新LocalStorage,保持客户端缓存与服务端一致
                await _localStorage.SetItemAsync("currentUser", sessionResult);

                return new AuthenticationState(authenticatedUser);
            }
            else
            {
                // Cookie已过期,清空客户端缓存
                await _localStorage.RemoveItemAsync("currentUser");
                return _anonymousState;
            }
        }
        catch (Exception)
        {
            // 网络异常时,降级使用LocalStorage缓存(建议添加用户提示)
            var cachedUser = await _localStorage.GetItemAsync<SessionValidationResult>("currentUser");
            if (cachedUser?.IsAuthenticated == true)
            {
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, cachedUser.UserName)
                };
                var identity = new ClaimsIdentity(claims, "CookieAuthentication");
                return new AuthenticationState(new ClaimsPrincipal(identity));
            }
            return _anonymousState;
        }
    }

    // 提供主动更新认证状态的方法,供外部触发
    public async Task RefreshAuthenticationState()
    {
        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }
}

// 用于接收服务端返回的会话校验结果
public class SessionValidationResult
{
    public bool IsAuthenticated { get; set; }
    public string UserName { get; set; }
}

3. 主动触发状态同步

为了避免客户端状态滞后,可在以下场景主动调用状态刷新:

  • 页面导航时:在MainLayout的OnInitializedAsync中添加定时检查(比Cookie过期时间早5分钟)
  • 用户操作敏感功能前:比如点击需要授权的按钮时

示例定时刷新实现:

@inject AuthenticationStateProvider AuthStateProvider

@code {
    protected override async Task OnInitializedAsync()
    {
        // 每25分钟刷新一次认证状态(Cookie过期时间设为30分钟)
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(25));
        while (await timer.WaitForNextTickAsync())
        {
            await ((CustomAuthenticationStateProvider)AuthStateProvider).RefreshAuthenticationState();
        }
    }
}

4. 登出逻辑同步

客户端登出时,不仅要清空LocalStorage,还要调用服务端登出接口清除Cookie:

// 客户端登出方法
public async Task Logout()
{
    // 调用服务端登出接口
    await _httpClient.PostAsync("api/auth/logout", null);
    
    // 清空客户端缓存并更新认证状态
    await _localStorage.RemoveItemAsync("currentUser");
    await ((CustomAuthenticationStateProvider)AuthStateProvider).RefreshAuthenticationState();
}

// 服务端登出接口
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return Ok();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 18:20:43