无感Token刷新核心是fetch响应拦截+双Token机制+请求暂存重放:401时用refresh_token换新access_token,通过isRefreshing标志防并发,暂存请求参数并重发,失败则清凭证跳登录。

用 fetch 实现无感 Token 刷新,核心是靠响应拦截逻辑 + 双 Token 机制 + 请求暂存重放,不依赖 Axios 等库,完全可控且轻量。
双 Token 机制是基础
必须后端配合提供两个 Token:
-
access_token:短期有效(如 30 分钟),每次请求放在
Authorization: Bearer xxx头里; -
refresh_token:长期有效(如 7 天),只用于换取新 access_token,建议存在
HttpOnly Cookie中(更安全),或加密后存 localStorage。
前端不解析、不校验 refresh_token,只在需要时原样提交给后端刷新接口(如 POST /auth/refresh)。
fetch 封装:统一处理 401 和刷新流程
不能每个 fetch 都手动判断 401,要封装一个自定义请求函数,内建拦截与恢复能力:
- 用
Promise链和闭包变量(如isRefreshing)控制刷新状态,避免并发触发多次刷新; - 遇到 401 时,若未在刷新中,就调用刷新接口;若已在刷新中,把当前请求“挂起”,加入待重试队列;
- 刷新成功后,更新本地 access_token(比如写入 sessionStorage),再批量重放队列里的请求;
- 刷新失败(如 refresh_token 也过期),清空凭证并跳转登录页。
关键细节:如何挂起并重放 fetch 请求
fetch 本身不可取消或重放,所以得把原始请求参数(url、method、headers、body)缓存下来,等新 token 到位后,用新 header 重新构造一次 fetch:
- 不要直接
await fetch(...)后再处理,而是封装成返回Promise<Response>的函数; - 队列中存的是“请求描述对象”+“resolve/reject 回调”,不是 Promise 实例本身;
- 重放时,需深拷贝 body(如 JSON 字符串需重新 parse 后 stringfy),避免因流已读导致报错;
- 对
GET或无 body 请求较简单;对POST/PUT含FormData或ArrayBuffer的,建议统一转为 JSON 提交,降低重放复杂度。
不推荐“提前预刷新”策略
有些方案会在 access_token 剩余 60 秒时主动刷新——看似平滑,实则风险高:
- 本地时间不准会导致误判(用户调快系统时间,token 显示“还剩 2 小时”,其实已过期);
- 频繁无效刷新增加后端压力;
- 无法覆盖网络延迟、服务响应慢等真实过期场景。
真正可靠的时机只有一个:后端明确返回 401 Unauthorized 并带标准语义(比如 {"code": 401, "message": "access token expired"})。

















