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

.NET 8 Blazor WebAssembly自定义认证状态不更新问题求助

问题核心分析

  1. 服务端错误注册了客户端专属的CustomAuthenticationStateProvider,导致依赖缺失(服务端无ISessionStorageService)启动失败
  2. 客户端认证状态处理存在SessionStorage键大小写不匹配、ClaimsIdentity未指定认证类型的问题,导致授权组件无法识别已登录状态

1. 修正服务端Program.cs配置

服务端的JWT认证仅用于保护API接口,无需注册客户端的AuthenticationStateProvider,同时需使用完整的授权服务而非轻量版:

builder.Services.AddAuthentication(o =>
{
    o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
    o.RequireHttpsMetadata = false;
    o.SaveToken = true;
    o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtAuthenticationManager.JWT_SECURITY_KEY)),
        ValidateAudience = false
    };
});
builder.Services.AddSingleton<UserAccountService>();
// 替换AddAuthorizationCore为服务端专用的AddAuthorization
builder.Services.AddAuthorization();
// 移除客户端专属的AuthenticationStateProvider注册
// builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
// 服务端无需BlazoredSessionStorage,可移除
// builder.Services.AddBlazoredSessionStorage();

2. 修复客户端CustomAuthenticationStateProvider问题

问题1:SessionStorage键大小写不匹配

统一键名避免大小写差异导致的读取失败;

问题2:ClaimsIdentity未指定认证类型

只有带认证类型的ClaimsIdentity才会被Blazor授权系统识别为已登录状态。

修改后的完整代码:

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly ISessionStorageService _sessionStorage;
    private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
    // 统一SessionStorage键名
    private const string SessionKey = "userSession";

    public CustomAuthenticationStateProvider(ISessionStorageService sessionStorage)
    {
        _sessionStorage = sessionStorage;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        try
        {
            var userSession = await _sessionStorage.ReadEncryptedItemAsync<UserSession>(SessionKey);
            if (userSession == null)
                return new AuthenticationState(_anonymous);
                
            var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
            {
                new Claim(ClaimTypes.Name, userSession.UserName),
                new Claim(ClaimTypes.NameIdentifier, userSession.Id.ToString())
            }, "JwtAuth"));
            return new AuthenticationState(claimsPrincipal);
        }
        catch 
        {
            return new AuthenticationState(_anonymous);
        }
    }

    public async Task UpdateAuthenticationState(UserSession? userSession)
    {
        ClaimsPrincipal claimsPrincipal;
        if (userSession != null)
        {
            claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
            {
                new Claim(ClaimTypes.Name, userSession.UserName),
                new Claim(ClaimTypes.NameIdentifier, userSession.Id.ToString())
            }, "JwtAuth")); // 与GetAuthenticationStateAsync保持一致的认证类型
            userSession.ExpiryTimeStamp = DateTime.Now.AddSeconds(userSession.ExpiresIn);
            await _sessionStorage.SaveItemEncryptedAsync(SessionKey, userSession); // 使用统一键名
        }
        else
        {
            claimsPrincipal = _anonymous;
            await _sessionStorage.RemoveItemAsync(SessionKey); // 使用统一键名
        }
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
    }

    public async Task<string> GetToken()
    {
        var result = string.Empty;
        try
        {
            var userSession = await _sessionStorage.ReadEncryptedItemAsync<UserSession>(SessionKey);
            if (userSession != null && DateTime.Now < userSession.ExpiryTimeStamp)
                result = userSession.Token;
        }
        catch
        {
        }
        return result;
    }
}

3. 确认客户端Program.cs配置

确保客户端仅注册必要服务:

builder.Services.AddBlazoredSessionStorage();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore();
// 若需调用API,确保注册HttpClient
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

4. 确认App.razor结构

确保应用根组件包含CascadingAuthenticationState,用于传递认证状态:

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <p>你没有权限访问此页面,请登录。</p>
                </NotAuthorized>
            </AuthorizeRouteView>
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>未找到</PageTitle>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>抱歉,未找到该页面。</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 09:35:02