Blazor Server JWT认证场景下如何不重载SPA实现客户端Token存储
问题核心原因
你遇到的两个报错本质都是服务端预渲染阶段操作客户端资源/响应流导致的,不需要重载SPA、不用操作Cookie和HttpContext的方案如下:
方案1:禁用当前登录页预渲染(最直接,无刷新)
你的SignIn页面默认触发预渲染才会导致IJSRuntime不可用,直接在页面顶部加指令关闭预渲染即可:
@* .NET 8 Blazor Web App 用下面这条 *@ @attribute [RenderModeServer(interactive: true, prerender: false)] @* .NET 8 之前的Blazor Server 用下面这条 *@ @attribute [Microsoft.AspNetCore.Components.Server.ServerRenderMode(prerender: false)]
关闭预渲染后页面加载就是完全交互模式,IJSRuntime已经完成初始化,可以直接调用操作客户端存储。
具体实现客户端存Token逻辑
首先修改你的页面代码,注入IJSRuntime,调整提交方法逻辑:
@page "/SignIn" @inject IJSRuntime JSRuntime @* 注入JS运行时 *@ <EditForm EditContext="@editContext" OnValidSubmit="SendRequestSubmit" class="auth__form"> <DataAnnotationsValidator /> <div class="auth__item"> <h4 class="auth__actions-title actions-title">Email<span>*</span></h4> <InputText @bind-Value="signInRequest.Email" class="input-actions" /> </div> <div class="auth__item"> <h4 class="auth__actions-title actions-title">Password<span>*</span></h4> <InputText @bind-Value="signInRequest.Password" type="password" class="input-actions" /> </div> <button class="auth__confirm actions-btn btn" disabled="@sendRequestLoading"> Sign In </button> </EditForm> @code { // 把返回值从async void改成async Task,避免异步异常漏捕获 private async Task SendRequestSubmit() { var auth = await accountSerrvice.AuthAsync(signInRequest.Email, signInRequest.Password); if(auth != null && !string.IsNullOrEmpty(auth.Token)) { // 持久化存储到localStorage,关闭浏览器也不会丢失 await JSRuntime.InvokeVoidAsync("localStorage.setItem", "auth_token", auth.Token); // 如果需要关闭浏览器就清空Token,替换为sessionStorage即可 // await JSRuntime.InvokeVoidAsync("sessionStorage.setItem", "auth_token", auth.Token); } else { // 这里不要直接抛异常,可换成前端友好提示 await JSRuntime.InvokeVoidAsync("alert", "账号或密码错误"); } } }
方案2:保留页面预渲染的兼容方案
如果你的登录页需要保留预渲染提升加载速度,可以通过生命周期判断交互状态再存储:
private bool _isInteractive = false; private string _pendingSaveToken = null; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _isInteractive = true; // 预渲染阶段拿到的Token,在交互阶段再写入存储 if (!string.IsNullOrEmpty(_pendingSaveToken)) { await JSRuntime.InvokeVoidAsync("localStorage.setItem", "auth_token", _pendingSaveToken); _pendingSaveToken = null; } } await base.OnAfterRenderAsync(firstRender); } private async Task SendRequestSubmit() { var auth = await accountSerrvice.AuthAsync(signInRequest.Email, signInRequest.Password); if(auth != null && !string.IsNullOrEmpty(auth.Token)) { if (_isInteractive) { await JSRuntime.InvokeVoidAsync("localStorage.setItem", "auth_token", auth.Token); } else { // 还在预渲染阶段就先暂存到变量,等进入交互阶段再写入 _pendingSaveToken = auth.Token; } } }
注意事项
- 不要用
async void作为异步方法返回值,会导致异常无法捕获、应用崩溃,统一改成async Task - localStorage是客户端明文存储,不要存敏感信息,Token要设置合理的过期时间
- 后续发起业务请求时,直接从localStorage读取Token放到请求头的Authorization字段即可,不需要操作HttpContext
内容的提问来源于stack exchange,提问作者Sergey Neklyudov
相关产品推荐
相关产品推荐

