Blazor 里 JWT 过期,用户正下单就被踢

发布时间:2026/10/10 6:15:16

Blazor 里 JWT 过期,用户正下单就被踢 用户在页面里填了一整页表单点了提交——啪跳回登录页。JWT 过期了刷新令牌没接上所有数据白填。这篇文章讲 Blazor 里怎么正确实现 JWT 刷新令牌让用户无感续期。先说为什么 Blazor 里这个问题特别恶心。在普通 SPAReact/Vue里JWT 存在 localStorage请求拦截器统一加 header401 时自动刷新——一套 Axios 拦截器搞定。但 Blazor 不一样Blazor Server 跑在服务端Blazor WebAssembly 跑在浏览器里两种模式下 token 的存储位置、刷新时机、信号传递方式完全不同。而且 Blazor 的组件状态是活的——用户在页面上操作时组件不会重新渲染你没法靠页面刷新来触发重新认证。必须在 HTTP 层静默处理。01 为什么 JWT 过期会踢人JWT 有过期时间exp claim。过期后API 服务器验证失败返回 401。如果你的应用不做刷新401 的处理就是跳登录页。问题是——JWT 的过期时间通常很短15-30 分钟这是安全设计。长过期时间 token 被盗后攻击窗口大。所以业界做法是短 access token 长 refresh tokenAccess Token — 15-30 分钟过期放 Authorization header Refresh Token — 7-30 天过期存安全位置httpOnly cookie / protected storage ↓ access token 过期时用 refresh token 换新的用户无感续期的流程是① 请求 API → 带 access token ② API 返回 401 → access token 过期了 ③ 拦截 401 → 用 refresh token 调 /refresh 端点换新 access token ④ 拿到新 token → 重放原始请求 ⑤ 用户完全无感表单数据不丢坑在哪Blazor 的 HttpClient 不是浏览器 fetch不能简单套 Axios 那套。Blazor Server 的 HttpClient 是 HttpClientFactory 创建的Blazor WebAssembly 的 HttpClient 是 WASM runtime 里的——两者拦截 401 的方式完全不同。02 Blazor ServerDelegatingHandler 拦截Blazor Server 跑在服务端HttpClient 可以套 DelegatingHandler——这是 .NET 原生的 HTTP 管道机制所有请求经过 handler 链。核心思路自定义一个AuthTokenHandler在 SendAsync 里自动加 token401 时自动刷新。public class AuthTokenHandler : DelegatingHandler { private readonly TokenService _tokenService; public AuthTokenHandler(TokenService tokenService) { _tokenService tokenService; } protected override async TaskHttpResponseMessage SendAsync( HttpRequestMessage request, CancellationToken ct) { // 1. 加 access token var token await _tokenService.GetAccessTokenAsync(); if (!string.IsNullOrEmpty(token)) request.Headers.Authorization new AuthenticationHeaderValue(Bearer, token); // 2. 发请求 var response await base.SendAsync(request, ct); // 3. 401 → 尝试刷新 if (response.StatusCode HttpStatusCode.Unauthorized) { var refreshed await _tokenService .TryRefreshTokenAsync(); if (refreshed) { // 4. 重放原始请求 var newToken await _tokenService .GetAccessTokenAsync(); request.Headers.Authorization new AuthenticationHeaderValue( Bearer, newToken); // 注意HttpClient 会缓存请求 // 需要克隆一份再发 response.Dispose(); var retry await CloneAndSendAsync( request, ct); return retry; } } return response; } private async TaskHttpResponseMessage CloneAndSendAsync( HttpRequestMessage req, CancellationToken ct) { var clone new HttpRequestMessage(req.Method, req.RequestUri); // 克隆 content如果有的话 if (req.Content ! null) { var bytes await req.Content.ReadAsByteArrayAsync(ct); clone.Content new ByteArrayContent(bytes); } foreach (var h in req.Headers) clone.Headers.TryAddWithoutValidation( h.Key, h.Value); return await base.SendAsync(clone, ct); } }关键细节请求克隆——HttpRequestMessage 发送后不能复用必须克隆一份再重发。这是最容易漏的一步漏了直接报请求已发送异常。并发刷新锁——多个请求同时 401 会触发多次刷新refresh token 可能被消耗多次如果是一次性 token。加一个SemaphoreSlim保证只刷新一次其他请求等结果。刷新失败处理——refresh token 也过期了跳登录页。但 Blazor Server 里不能直接NavigationManager.NavigateTo(/login)那是客户端导航需要通过 JS Interop 调location.href做整页跳转。// TokenService 里的并发刷新 private static readonly SemaphoreSlim _refreshLock new(1, 1); public async Taskbool TryRefreshTokenAsync() { await _refreshLock.WaitAsync(); try { // 双重检查也许别的线程刚刷过 if (IsAccessTokenValid()) return true; var refreshReq new { RefreshToken _refreshToken }; var resp await _httpClient.PostAsJsonAsync( /api/auth/refresh, refreshReq); if (!resp.IsSuccessStatusCode) { // refresh token 也过期 → 强制登出 await _jsRuntime.InvokeVoidAsync( location.replace, /login?expired1); return false; } var tokens await resp.Content .ReadFromJsonAsyncTokenResponse(); _accessToken tokens.AccessToken; _refreshToken tokens.RefreshToken; return true; } finally { _refreshLock.Release(); } }03 Blazor WebAssembly自定义 HttpMessageHandlerWASM 模式下思路一样但实现有区别——token 存在浏览器里而且没有SemaphoreSlim的跨组件同步问题因为 WASM 是单线程的。存储选择// Program.cs — 注册带 token 拦截的 HttpClient builder.Services.AddScopedAuthTokenHandler(); builder.Services.AddScoped(sp { var handler sp.GetRequiredServiceAuthTokenHandler(); handler.InnerHandler new HttpClientHandler(); return new HttpClient(handler) { BaseAddress new Uri(builder.HostEnvironment.BaseAddress) }; }); // Token 存 protected localStorage // Blazor WASM 的 ProtectedLocalStorage 在 Server 端 // WASM 用 IJSRuntime 直接调 localStorage API builder.Services.AddSingletonITokenStorage, LocalTokenStorage();WASM 里的 handler 和 Server 版几乎一样区别在刷新失败时的跳转——WASM 可以直接用NavigationManager做客户端导航不需要 JS Interop。WASM 专属坑localStorage 是异步的但DelegatingHandler.SendAsync也是异步的没问题。但如果你在OnInitializedAsync里调 API刷新 重放的延迟会导致组件渲染两次——务必在组件里处理 Loading 状态否则用户会看到一闪而过的错误。04 两种模式都要注意的三件事① refresh token 的存储位置Blazor Server存在服务端内存 / Session / Redis。别存 cookie——Blazor Server 用 SignalR 连接cookie 跟着连接走断线重连会丢。Blazor WASM存 localStorage。XSS 风险WASM 默认不做dangerouslySetInnerHTMLXSS 攻击面比传统 SPA 小。但如果你的 WASM 应用加载了第三方 JS 模块仍有风险。折中方案access token 存内存refresh token 存 localStorageJS 模块拿不到内存里的 access token。② 刷新端点的设计POST /api/auth/refresh Body: { refreshToken: xxx } Response: { accessToken: 新 access token, refreshToken: 新 refresh token // 轮换 }refresh token 必须轮换——每次刷新后旧的失效返回新的。不轮换 refresh token 被盗后永久有效。进一步可以加 refresh token rotation reuse detection如果检测到旧 token 被使用说明可能被窃取立即吊销整个 token 家族。③ 预刷新别等 401 再刷等 401 再刷的问题是——那个请求已经失败了。虽然重放能恢复但用户可能看到一瞬间的错误状态。更好的做法是预判过期在发请求前检查 access token 的 exp claim如果快过期了比如 5 分钟内先刷新再发请求。// TokenService 里加预判 public async Taskstring GetAccessTokenAsync() { if (_accessToken ! null) { var jwt new JwtSecurityToken(_accessToken); // 过期前 5 分钟刷新 if (jwt.ValidTo DateTime.UtcNow.AddMinutes(5)) return _accessToken; } // 快过期了 → 主动刷新 await TryRefreshTokenAsync(); return _accessToken; }这样AuthTokenHandler里GetAccessTokenAsync自带预判401 分支只是兜底——正常情况下永远不会走到 401 分支。Blazor JWT 刷新令牌核心就四步DelegatingHandler 拦截 → 401 自动刷新 → 请求重放 → 预判过期抢先刷用户从头到尾无感表单数据不丢。收藏这篇下次别再让用户填了一半被踢出去。refresh token 接好用户体验直接上一个台阶。关注 CSharp精选营每周二四 get 能直接抄的 C# 实战。阅读原文Blazor 里 JWT 过期用户正下单就被踢 - 码录集
延伸阅读

更多相关文章

2026/10/10 6:15:16

安卓编译器神器,编程学习随时随地

安卓手机上存在那些办公软件性质的编译器, 它们的页面工具具有比较高的还原度, 精细的画面展示在大家眼前, 让用户高效利用手机打开软件, 从而完成一系列的工作, 减少时间和特殊情况, 正在学习编程的用户, 在下载使用之后, 任何时间都可以进行练习, 赶紧有需要的体验编程的用户…

2026/10/10 6:10:16

iptables规则不生效?从计数器、LOG追踪到tcpdump的完整排查实战

说个场景:你给 iptables 加了一条放行规则,逻辑推演了几遍都觉得没问题,结果测试的时候流量就是不通。更气人的是,你用iptables -L -n看规则明明就在那里,但业务毫无反应。这种“规则存在但等于没存在”的状态&#xf…

2026/10/10 7:15:20

Codex CLI接入OpenAI兼容接口:config.toml配置与排错

如果你手头有 Codex CLI,又不想只接固定的云上模型,今天这篇文章值得你花五分钟看完。我会把config.toml逐行拆开讲,覆盖接入 OpenAI 兼容接口时的常见报错和排查思路,也算是我这半年反复折腾下来的一份笔记。文章面向两类人&…

2026/10/10 7:15:20

Cursor高效配置四步法:用规则驱动代码减量

1. 项目概述:这不是在教你怎么点开设置,而是在重建你和代码的协作关系“Cursor怎么配置才好用?这套规则让我少写一半代码”——这句话我第一次看到时,手停在键盘上三秒。不是因为夸张,而是太真实。过去两年&#xff0c…

2026/10/10 7:15:20

Claude作为创业决策协作者的实战闭环构建

1. 项目概述:这不是“AI创业课”,而是一份面向真实创业者的协作增强方案“Claude 创业计划扩展至更多创始人”——这个标题乍看像一则新闻通稿,但作为连续三年深度参与多个早期技术型创业项目孵化的从业者,我第一反应是&#xff1…

2026/10/10 7:15:20

用PINN做多变量回归预测:Matlab实现与调参全攻略

用PINN做多变量回归预测,还是多输入单输出,Matlab代码该怎么落地?这个问题我刚接触的时候绕了不少弯路。物理信息神经网络(PINN)这两年讨论度很高,核心其实一句话:让神经网络的预测结果不仅拟合…

2026/10/10 7:15:20

HED边缘检测实战:从VGG16到多任务学习的深度学习流水线

简介:HED_edgeDetect 是一份面向计算机视觉初学者与深度学习实践者的边缘检测资源包,围绕 HED(Hypercolumns for Edge Detection)这一基于卷积神经网络的端到端边缘检测方法展开,可用于理解多尺度特征融合、预训练与微…

2026/10/10 7:10:19

Python+Pillow制作艺术签名生成器:字体渲染与透明PNG实战

我一直在用 Python 做一些小工具,最近琢磨着一个挺有意思的需求——给自己设计一个优雅的艺术签名。平时签文件、写卡片、做水印,总觉得自己手写签名不够好看,或者干脆没有手写习惯,一笔一画下去自己都嫌弃。于是我用 Pillow 做了…

2026/10/8 10:03:18

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/9 20:15:56

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/8 6:05:44

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 0:04:53

从逻辑门到计算机:数字电路核心原理与全加器搭建实战

如果你拆过一台旧电脑的主板,盯着那些黑乎乎的小芯片看上一会儿,可能会冒出同一个疑问:这堆引脚密集的元件,到底是怎么“变”出那么复杂的应用的?答案并不在某个神秘的部件里,而是在所有芯片内部都在反复使…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑