news 2026/9/9 9:46:04

一个 Token 就够了,JWT 续签为什么要搞 Access Token + Refresh Token 双 Token?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一个 Token 就够了,JWT 续签为什么要搞 Access Token + Refresh Token 双 Token?

JWT 做登录认证,有一点很多人不理解,登录成功发一个 Token,前端每次请求带上,后端验签,过期了重新登录。感觉一个 Token 就能解决所有问题了。

那为什么 OAuth 2.0 规范里非搞了Access TokenRefresh 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 已经失效而触发异常,服务端可以立刻发现并强制用户重新登录。

后端实现的核心逻辑

不贴完整的代码了,说清楚关键点。

登录接口,同时生成两个 Token:

public 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 Rotation,OAuth 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 这些是典型场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 9:45:49

源代码论文分享|校园便利平台,适合毕设/课设参考!

如果你正在做毕业设计,又不想选太传统的管理系统,校园便利平台其实是一个比较贴近学生真实需求的方向。 这种项目的好处是场景很熟悉。二手交易、失物招领、跑腿、校园信息、生活服务,这些内容都能围绕“校园便利”自然展开。相比单纯做一个后…

作者头像 李华
网站建设 2026/8/31 2:28:44

AI商品发现开放协议:从意图解析到推荐理由的标准化设计

AI-mediated product discovery,也就是由AI介入的商品发现过程,最近讨论度很高。很多人第一反应是“再训一个推荐模型”,但实际落地时你会发现,真正的瓶颈往往不在算法,而在数据怎么进、意图怎么传、结果怎么回。这也是…

作者头像 李华
网站建设 2026/8/30 15:44:44

第14章 安全与命名空间:用户态边界的形成

第14章 安全与命名空间:用户态边界的形成 内核版本:Linux 7.1.3 架构:x86_64 核心源码路径: security/security.c security/commoncap.c security/lsm_init.c kernel/user_namespace.c kernel/nsproxy.c kernel/audit.c 用户态不是“自然可信”的。PID 1 进入用户态之后,…

作者头像 李华
网站建设 2026/8/31 11:17:55

把你的烂API救回来:Go版生产级可观测性四板斧

凌晨2点,你的API炸了。你能找到是哪个请求挂了吗?能给客户一个工单号吗?能在接口下线前提醒他们吗? 大部分API对这三个问题都说NO。不是因为代码写得烂——是因为可观测性一开始就没设计进去。下面这四招,专治各种&quo…

作者头像 李华
网站建设 2026/8/31 11:50:05

熵权法:基于信息熵的客观赋权方法在评价模型中的应用

1. 项目概述:为什么评价类模型绕不开熵权法?做数学建模,尤其是评价类问题,你迟早会碰到一个名字听起来有点玄乎但用起来真香的方法——熵权法。我第一次接触它是在一个关于城市综合发展水平评价的赛题里,当时面对一堆经…

作者头像 李华
网站建设 2026/8/30 23:31:51

【LLM开发实验】LLM推理基本介绍

目录 显存需求估算:参数量经验法则 基本计算 激活内存 激活占用显存 (VRAM) 键值(KV)缓存 关于权重 (weight)的考量 推理 (inference)与训练的 FLOPS 容量和速度都重要 量化简介 权衡:准确性 显存需求估算:参…

作者头像 李华