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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 01:24:00