三公自动算账 在 Blazor 应用中,用户在进行关键操作(如下单)时因 JWT 过期被强制登出

在 Blazor 应用中,用户在进行关键操作(如下单)时因 JWT 过期被强制登出,通常是因为前端 Token 静默刷新失败,或者后端对过期 Token 的拦截过于强硬。Blazor Server 和 Blazor WebAssembly (WASM) 的处理机制不同,需针对性解决。


以下是针对两种托管模式的解决方案及最佳实践:


1. 核心原因分析

静默刷新失败‌:Access Token 过期后,前端尝试使用 Refresh Token 获取新 Token 时失败(网络问题、Refresh Token 也过期、或逻辑未触发)。

HTTP 拦截器缺失‌:API 请求发出时携带了过期的 Access Token,后端返回 401,前端未捕获并尝试刷新,直接跳转登录页。

Blazor Server 的 HttpContext 陷阱‌:在 Blazor Server中,初始加载后 HttpContext 不再可用,若依赖 Cookie 中的 JWT 进行后续 SignalR 通信验证,容易因 Cookie 过期或状态不同步导致断开连接。

2. Blazor WebAssembly (WASM) 解决方案


WASM 是完全客户端渲染,JWT 通常存储在内存或 LocalStorage 中。关键在于‌实现自动静默刷新机制‌。


A. 配置 OIDC 静默刷新


确保 Program.cs 中配置了 AuthenticationOptions 以支持静默刷新:


csharp

builder.Services.AddOidcAuthentication(options =>

{

    builder.Configuration.Bind("Local", options.ProviderOptions);

    // 关键:允许静默刷新

    options.ProviderOptions.ResponseType = "code"; 

    options.UserOptions.RoleClaim = "role";

});


B. 自定义 HttpClientHandler 实现自动重试


创建一个 DelegatingHandler,在收到 401 时尝试刷新 Token 并重试请求:


csharp

public class JwtRefreshHandler : DelegatingHandler

{

    private readonly IAccessTokenProvider _tokenProvider;


    public JwtRefreshHandler(IAccessTokenProvider tokenProvider)

    {

        _tokenProvider = tokenProvider;

    }


    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

    {

        var response = await base.SendAsync(request, cancellationToken);


        if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)

        {

            // 尝试获取新 Token

            var tokenResult = await _tokenProvider.RequestAccessToken();

            

            if (tokenResult.TryGetToken(out var token))

            {

                // 更新 Header 并重试

                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);

                response = await base.SendAsync(request, cancellationToken);

            }

        }


        return response;

    }

}



在 Program.cs 注册:


csharp

builder.Services.AddScoped<JwtRefreshHandler>();

builder.Services.AddScoped(sp => 

{

    var handler = sp.GetRequiredService<JwtRefreshHandler>();

    handler.InnerHandler = new HttpClientHandler();

    return new HttpClient(handler) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) };

});


C. 下单前的主动检查


在用户点击“提交订单”按钮时,先检查 Token 状态,若即将过期则提前刷新:


csharp

private async Task SubmitOrder()

{

    // 主动请求 Token,如果过期会自动触发刷新流程

    var tokenResult = await AccessTokenProvider.RequestAccessToken();

    

    if (tokenResult.TryGetToken(out var token))

    {

        // 执行下单 API 调用

        await Http.PostAsJsonAsync("/api/orders", orderModel);

    }

    else

    {

        // 刷新失败,提示用户重新登录

        NavigationManager.NavigateTo("/authentication/login");

    }

}


3. Blazor Server 解决方案


Blazor Server 通过 SignalR 保持长连接,JWT 处理更为复杂。‌最佳实践是避免在 Blazor Server 中直接使用 JWT 作为主要认证机制,而是使用 Cookie 认证 + 服务器端 Session。‌


A. 推荐架构:Cookie + 服务器端 Session


不要将 JWT 存储在客户端或由前端管理。让 ASP.NET Core 的身份验证中间件处理 Cookie,并在服务器端维护用户状态。


配置 Cookie 认证‌:


csharp

builder.Services.AddAuthentication(options =>

{

    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;

})

.AddCookie(options =>

{

    options.ExpireTimeSpan = TimeSpan.FromMinutes(30);

    options.SlidingExpiration = true; // 滑动过期,用户活动即续期

});



API 层使用 JWT(如果需要)‌:

如果 Blazor Server 需要调用外部 API,应在服务器端使用 IHttpClientFactory 获取 Token,而不是暴露给前端。


B. 如果必须使用 JWT (混合模式)


若必须在前端持有 JWT,需注意 HttpContext 仅在首次加载时有效。


避免在组件构造函数中读取 User‌:

由于 SignalR 连接建立后 HttpContext 为 null,应使用 AuthenticationStateProvider 来获取用户信息。


处理 SignalR 断开‌:

JWT 过期可能导致 SignalR 连接中断。配置 SignalR 重连策略:


csharp

builder.Services.AddRazorComponents()

    .AddInteractiveServerRenderMode(options =>

    {

        options.DetailedErrors = true;

    });



使用 Refresh Token 旋转‌:

如搜索结果所述,避免在 Cookie 中存储大型 JWT。使用‌引用令牌 (Reference Tokens)‌ 或在服务器端分布式缓存(如 Redis)中存储 Session ID,Cookie 仅存储 Session ID。这样即使 JWT 过期,服务器端仍可控制会话有效性,并通过滑动过期机制延长用户会话。


4. 通用最佳实践:防止下单中断


无论哪种模式,以下措施可显著提升用户体验:


滑动过期 (Sliding Expiration)‌:

确保后端配置了滑动过期。只要用户在会话期间有活动,Token/Session 有效期就自动延长。


前端乐观 UI + 错误回滚‌:


用户点击下单后,立即显示“处理中...”状态。

如果 API 返回 401,前端自动尝试刷新 Token 并重发请求。

如果刷新成功且重发成功,用户无感知。

如果刷新失败,才跳转登录页,并保留用户填写的表单数据(使用 LocalStorage 或 SessionStorage 暂存草稿)。


预检 Token 状态‌:

在关键操作前,检查 Token 的 exp 声明。如果剩余时间小于阈值(如 1 分钟),主动发起刷新请求。


后端幂等性设计‌:

确保下单接口具有幂等性。如果因网络波动或 Token 刷新导致请求重发,不会创建重复订单。


总结建议

Blazor WASM‌:实现 DelegatingHandler 自动捕获 401 并刷新 Token,同时在关键操作前主动检查 Token 有效性。

Blazor Server‌:优先使用 ‌Cookie 认证 + 滑动过期‌,避免前端直接管理 JWT。如果必须调用受保护的 API,请在服务器端代理请求,由服务器负责 Token 的管理和刷新。

用户体验‌:始终在本地暂存用户输入,防止因认证中断导致数据丢失。


0 评论

发表评论