React前端如何基于后端refreshToken实现1分钟有效期的accessToken自动刷新
React 端自动刷新Access Token的规范实现方案
先点破一个大家最容易踩的坑:上来就写个固定60秒触发的setInterval做token刷新,本质上是完全不规范的。结合你后端固定1分钟有效期的accessToken、独立refreshToken换发接口的设计,最合理的实现逻辑是基于token剩余有效期动态触发 + 单例刷新锁防并发请求 + 多场景异常兜底,具体落地方式如下:
核心设计原则
- 避免无效重复刷新:不要不管当前token是否可用,到点就硬调刷新接口
- 避免过期请求漏网:不要等后端返回401了才被动触发刷新
- 解决多标签页、页面切后台导致的定时器失效问题
具体落地实现步骤
统一封装Token存储模块
不要在各个业务组件里零散操作本地存储,把token的读写、过期时间管理统一收敛到单独的工具模块里:- 持久化存储两个核心值:
accessToken、accessTokenExpireAt(这个值不需要后端返回,前端在拿到accessToken的时候直接计算:当前时间戳 + 60*1000,因为后端固定accessToken是1分钟有效期) - refreshToken根据部署场景调整存储方式:同域前后端优先存在HttpOnly Cookie里避免XSS窃取;跨域场景存在localStorage时,要做好多标签页的同步逻辑。
- 持久化存储两个核心值:
实现带锁的单例刷新函数
写专门的刷新方法,通过状态锁保证整个页面同一时间只会发一次刷新请求,其他等待的请求会在刷新完成后复用结果,不会出现并发调用把refreshToken刷失效的问题:let isRefreshing = false const pendingCallbacks = [] async function refreshAccessToken() { // 已有刷新请求在跑的话,把当前调用挂起,等刷新完拿结果 if (isRefreshing) { return new Promise(resolve => pendingCallbacks.push(resolve)) } isRefreshing = true try { const res = await fetch('/Token', { method: 'POST', body: JSON.stringify({ refreshToken: getRefreshToken() }) }) const newAccessToken = res.data.accessToken // 写入新的token和对应的过期时间 saveAccessToken(newAccessToken, Date.now() + 60 * 1000) // 释放所有挂起的调用 pendingCallbacks.forEach(cb => cb(newAccessToken)) pendingCallbacks.length = 0 return newAccessToken } finally { isRefreshing = false } }动态调度刷新定时器
在用户完成SignIn/Login拿到第一组token后,不要启动永久运行的全局定时器,而是根据token的过期时间,动态计算下一次刷新的等待间隔,提前5秒触发刷新(预留网络请求的时间差,避免刚好卡着过期点发请求):let refreshTimer = null function scheduleTokenRefresh(expireAt) { // 清除上一次的定时器,避免定时器叠加 if (refreshTimer) clearTimeout(refreshTimer) // 计算等待时长:过期时间 - 提前刷新的5秒 - 当前时间,最小等待时间为0 const waitDuration = Math.max(expireAt - 5 * 1000 - Date.now(), 0) refreshTimer = setTimeout(async () => { try { await refreshAccessToken() // 刷新成功后,递归调度下一次刷新 scheduleTokenRefresh(getAccessTokenExpireTime()) } catch (err) { // 刷新失败说明refreshToken已失效,执行登出逻辑清除本地凭证 triggerLogout() } }, waitDuration) }请求拦截层兜底
在统一的请求封装层加拦截判断:每个发往后端的请求发出前,先检查当前accessToken的状态——如果已经过期,就先等待刷新完成拿到新token,再带着新token把原请求发出去,不要等后端返回401再做补救。异常场景兼容
- 页面切回前台的检查:浏览器对后台页面的定时器会做节流,当用户从后台切回当前页面时,主动检查一次当前token的过期状态,如果已经过期就立刻触发刷新,不要等定时器自然唤醒。
- 多标签页同步:监听
storage事件,当其他标签页更新了本地存储里的accessToken和过期时间,当前标签页同步更新本地的token状态,避免重复调用刷新接口。
常见非规范实现的问题
- 用固定间隔的
setInterval每60秒刷一次:很容易因为请求耗时、浏览器定时器节流的问题,出现带着过期token发请求的情况;而且即使用户已经退出登录,只要页面没关,定时器还会在后台空跑。 - 只在401响应拦截器里做被动刷新:会导致用户的第一次请求带着过期token发出去,后端返回401,前端再重发一次,不仅有额外的请求延迟,还可能遇到非幂等接口重复提交的问题。
- 把刷新逻辑散在各个业务组件里:容易出现重复调用刷新接口、未登录状态下误触发刷新的问题。
内容的提问来源于stack exchange,提问作者user14047881
相关产品推荐
相关产品推荐

