一个 Token 就够了,JWT 续签为什么要搞 Access Token + Refresh Token 双 Token?
JWT 做登录认证有一点很多人不理解登录成功发一个 Token前端每次请求带上后端验签过期了重新登录。感觉一个 Token 就能解决所有问题了。那为什么 OAuth 2.0 规范里非搞了Access Token和Refresh Token两个因为单 Token 方案有一个绕不开的点那就是 Token 的有效期。单 Token 的问题Token 用于身份认证必须给它设一个过期时间。这个过期时间怎么设就是问题所在。设短了用户体验不好了设 30 分钟用户正在填一个复杂的表单填了 25 分钟点提交的时候 Token 过期了直接跳转到登录页表单数据全没了。谁都得想骂人。设长了安全风险高了设 7 天如果这个 Token 被泄露了抓包、XSS 攻击、用户在公共电脑上没退出登录——攻击者拿着这个 Token 可以用 7 天。在这 7 天里你没有任何办法让它失效因为 JWT 是无状态的服务端不存 Session没地方去标记这个 Token 作废了。也可以搞个黑名单Token 被盗就加入黑名单。这样也行但这会使得每次请求都要查一遍黑名单JWT 的无状态、不用查库的优势就没了又干回到了Session的模式只不过换了个存储的地方。单 Token 方案的根本矛盾过期时间越短越安全但用户体验越差过期时间越长体验越好但安全风险越大。没法同时满足两头。双 Token 如何解决思路很直接既然一个 Token 兼顾不了安全和体验那就拆成两个一个管安全一个管体验。Access Token过期时间很短通常 15-30 分钟。前端每次请求带它去访问业务接口。因为有效期短即使被泄露了攻击者能利用的窗口也很小。Refresh Token过期时间很长通常 7-30 天。它只有一个用途在 Access Token 过期之后用它去换一个新的 Access Token。它不能直接访问业务接口。整个流程是这样的用户体验上只要 Refresh Token 没过期用户永远不会被踢出去。哪怕 Access Token 15 分钟就过期一次前端自动刷新用户根本感觉不到。安全性上真正在网络上高频传输的是 Access Token它即使被截获也只有 15-30 分钟的有效期。Refresh Token 只在刷新的时候传输一次暴露面小得多。为什么 Refresh Token 更安全Refresh Token 有效期那么长它被偷了不是一样危险确实但 Refresh Token 有几个特点让它被盗的概率远低于 Access Token传输频率低Access Token 每次请求都要带上一天可能传输几百上千次。Refresh Token 只在 Access Token 过期时才传输一次一天可能就传几次。传输次数越少被中间人截获的概率越低。存储方式可以不同Access Token 通常放在内存或者localStorage里方便前端随时取用。Refresh Token 可以存在HttpOnly Cookie里JavaScript访问不到XSS 攻击偷不走它。可以做更严格的校验Refresh Token 请求刷新接口时服务端可以额外校验设备指纹、IP 地址是否一致。如果发现 Refresh Token 在一个陌生 IP 上使用直接拒绝并让用户重新登录。这种重校验对 Access Token 做的话成本太高每个请求都校验设备指纹影响性能但 Refresh Token 只在刷新时才用一天就几次完全扛得住。可以做 Refresh Token 轮换每次用 Refresh Token 换新的 Access Token 时同时下发一个新的 Refresh Token旧的立即作废。这样即使 Refresh Token 被偷了攻击者用了一次之后正常用户下一次刷新就会因为旧 Token 已经失效而触发异常服务端可以立刻发现并强制用户重新登录。后端实现的核心逻辑不贴完整的代码了说清楚关键点。登录接口同时生成两个 Tokenpublic LoginResponse login(String username, String password) { // 验证用户名密码... String accessToken jwtUtil.generateToken(userId, Duration.ofMinutes(30)); String refreshToken UUID.randomUUID().toString(); // Refresh Token 存 Redis关联用户信息 redis.opsForValue().set( refresh: refreshToken, userId, 7, TimeUnit.DAYS ); returnnew LoginResponse(accessToken, refreshToken); }注意 Refresh Token 不一定是 JWT。很多人以为两个都是 JWT其实 Refresh Token 用一个随机字符串就行真正的状态存在服务端的 Redis 里。这样你可以随时在服务端让它失效——用户点了退出登录直接删掉 Redis 里的 Refresh Token 立刻生效。如果 Refresh Token 也用 JWT 且不存服务端那又回到了无法主动让它失效的老问题。刷新接口public TokenResponse refresh(String refreshToken) { // 从 Redis 查 Refresh Token 对应的用户 String userId redis.opsForValue().get(refresh: refreshToken); if (userId null) { thrownew AuthException(Refresh Token 已过期请重新登录); } // 生成新的 Access Token String newAccessToken jwtUtil.generateToken(userId, Duration.ofMinutes(30)); // Refresh Token 轮换旧的删掉发一个新的 redis.delete(refresh: refreshToken); String newRefreshToken UUID.randomUUID().toString(); redis.opsForValue().set( refresh: newRefreshToken, userId, 7, TimeUnit.DAYS ); returnnew TokenResponse(newAccessToken, newRefreshToken); }每次刷新都换一个新的 Refresh Token旧的立刻删掉。这是 Refresh Token RotationOAuth 2.0 安全最佳实践里推荐的做法。前端实现前端要做的核心是拦截 401 响应自动刷新然后重放失败的请求。用 Axios 拦截器来实现let isRefreshing false; let pendingRequests []; axios.interceptors.response.use( response response, async error { const originalRequest error.config; if (error.response?.status 401 !originalRequest._retry) { if (isRefreshing) { // 已经在刷新了把请求排队 returnnewPromise(resolve { pendingRequests.push(token { originalRequest.headers.Authorization Bearer token; resolve(axios(originalRequest)); }); }); } originalRequest._retry true; isRefreshing true; try { const { data } await axios.post(/auth/refresh, { refreshToken: getRefreshToken() }); setAccessToken(data.accessToken); setRefreshToken(data.refreshToken); // 把排队的请求全部用新 Token 重发 pendingRequests.forEach(cb cb(data.accessToken)); pendingRequests []; originalRequest.headers.Authorization Bearer data.accessToken; return axios(originalRequest); } catch (e) { // Refresh Token 也过期了跳登录 clearTokens(); window.location.href /login; returnPromise.reject(e); } finally { isRefreshing false; } } returnPromise.reject(error); } );这段代码有个关键细节isRefreshing标志位和pendingRequests队列。Access Token 过期的瞬间页面上可能同时有好几个请求都返回了 401。如果每个 401 都去调一次刷新接口就会并发刷新前一个刷新拿到的 Refresh Token 刚换完就被后一个刷新请求用旧的 Token 去调直接失败。所以要保证只有第一个 401 触发刷新其他的排队等着等新 Token 拿到了统一重发。什么时候不需要双 Token双 Token 不是所有场景都值得搞。内部管理后台用户就那几十个运营人员安全要求没那么极端Token 过期了重新登录也不是什么大事。单 Token 设个 2 小时过期完全够用。短期活动页面H5 活动页可能就活一周搞双 Token 的工程量大于它的收益。已经用了 Session 的项目如果你的系统本来就是有状态的Session 存在 Redis 里那 Token 只是 Session ID 的另一种表现形式。Session 机制天然支持服务端主动失效双 Token 解决的问题对你来说不存在。双 Token 真正有价值的场景是无状态的 JWT 认证 用户量大 安全性要求高 用户体验不能打折扣。移动端 App、SaaS 产品、开放平台 API 这些是典型场景。

相关新闻

微软:解耦架构赋能智能体强化学习

微软:解耦架构赋能智能体强化学习

📖标题:Agent Lightning v1.0: Towards Harnessed Agentic RL 🌐来源:arXiv, 2608.17528v1 🛎️文章简介 🔸研究问题:在智能体强化学习中,如何解决因训练引擎与部署时智能体框架&am…

2026/10/25 19:43:00 阅读更多 →
Kubernetes生产运维08:Ingress报404和502,怎么顺着七层链路分清是路由错还是后端挂

Kubernetes生产运维08:Ingress报404和502,怎么顺着七层链路分清是路由错还是后端挂

Kubernetes生产运维08:Ingress报404和502,怎么顺着七层链路分清是路由错还是后端挂 写在前面 Ingress报错时,很多人不区分404和502,一律去重启Ingress控制器或改规则试。 其实这两个错误码指向完全不同的位置。粗略地说&#xf…

2026/10/23 13:41:31 阅读更多 →
Kubernetes生产运维07:CoreDNS解析慢或解析失败,怎么顺着ndots和搜索域找到真因

Kubernetes生产运维07:CoreDNS解析慢或解析失败,怎么顺着ndots和搜索域找到真因

Kubernetes生产运维07:CoreDNS解析慢或解析失败,怎么顺着ndots和搜索域找到真因 写在前面 DNS一出问题,很多人的第一反应是CoreDNS是不是挂了,然后去重启CoreDNS。 但DNS故障的表现有三种,根因完全不同:解…

2026/10/24 6:15:08 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/1 0:24:43 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/1 10:36:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/1 10:36:51 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/1 1:06:58 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/1 10:35:11 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/1 9:35:41 阅读更多 →

日新闻

周新闻

月新闻