请求前校验Token有效性还是响应后处理?哪种刷新Access Token方式更优?
Token校验与刷新的最佳实践分析
问题1:该在请求前校验Token,还是等响应后再校验?
两种方式各有用处,实际项目里建议两者结合:
- 请求前校验:
- 好处:提前拦住明显过期的Token,不用发无效请求给服务器,省资源也能更快给用户反馈。
- 不足:只能判断Token是否过期,管不了服务器端主动废掉的Token(比如账号被封、Token被拉黑),这种情况还是会收到401。
- 响应后校验:
- 好处:是兜底方案,不管服务器因为啥原因判定Token无效(包括本地没检测到的情况),都能处理。
- 不足:会多一次无效请求,但这点损耗基本可以忽略。
问题2:请求拦截器提前刷新,还是响应拦截器抓401后刷新?
推荐以响应拦截器捕获401为主,请求拦截器校验为辅,原因如下:
请求拦截器提前刷新的坑
- 时间差问题:本地时间和服务器时间可能不一样,比如本地觉得Token还能用,但服务器已经判它过期了,还是会返回401;或者本地误判过期,白刷一次Token。
- Token有效期不同步:Id Token和Access Token的过期时间未必一致,比如Access Token还没到期,但Id Token先过期了,这时候提前刷新纯属鸡肋;反过来Access Token过期但Id Token没过期,本地校验会漏掉,还是会触发401。
- 并发请求重复刷新:多个请求同时触发的话,会反复调用
refreshTokens(),造成多余的刷新请求,还得额外加锁控制。
响应拦截器抓401的优势
- 准没错:只有服务器明确返回401,才触发刷新,完全跟着服务器的判定走,不会出错。
- 逻辑简单:不用纠结本地时间、Token有效期不同步这些破事,只处理服务器给的明确错误就行。
- 并发好处理:加个
isRefreshing标记就能避免重复刷新,刷新完还能把之前失败的请求都重发一遍,用户体验更好。
你的代码需要调整的地方
现有代码有几个小问题:
- 请求拦截器里
await refreshTokens()之后,没重新拿最新的accessToken,等于还是用旧Token发请求,白刷了。 - 响应拦截器里
refreshTokens()没加await,也没重发原请求,用户的操作直接就失败了。 - 没处理并发请求重复刷新的情况。
修正后的参考代码:
let isRefreshing = false; let failedRequests = []; axios.interceptors.request.use( async config => { const idToken = localStorage.getItem('IdToken'); if (idToken && checkTokenExpired(idToken)) { // 提前刷新,加锁避免并发重复刷新 if (!isRefreshing) { await refreshTokens(); } else { // 等待刷新完成再继续 await new Promise(resolve => failedRequests.push(resolve)); } } // 一定要重新获取最新的accessToken const accessToken = localStorage.getItem('accessToken'); config.headers['token'] = accessToken; return config; }, error => { return Promise.reject(error); } ); axios.interceptors.response.use( res => { return res; }, async err => { const originalConfig = err.config; // 只处理未重试过的401请求,避免死循环 if (err.response?.status === 401 && !originalConfig._retry) { originalConfig._retry = true; if (!isRefreshing) { isRefreshing = true; try { await refreshTokens(); // 刷新成功后,重发所有排队的请求 failedRequests.forEach(resolve => resolve()); failedRequests = []; // 重发当前的原始请求 return axios(originalConfig); } catch (_error) { // 刷新失败,直接跳登录页 localStorage.removeItem('IdToken'); localStorage.removeItem('accessToken'); window.location.href = '/login'; return Promise.reject(_error); } finally { isRefreshing = false; } } else { // 加入请求队列,等刷新完成后再重发 return new Promise(resolve => { failedRequests.push(() => { resolve(axios(originalConfig)); }); }); } } return Promise.reject(err); } ); const checkTokenExpired = idToken => { if (!idToken) return false; const decoded = jwtDecode(idToken); if (!decoded?.exp) return false; // 提前1秒判定过期,避免网络延迟导致刚好过期的请求发送 return Date.now() + 1000 >= decoded.exp * 1000; };
总结
- Token校验:请求前做本地过期检查,响应后抓401兜底,既省资源又能覆盖所有场景。
- Token刷新:优先用响应拦截器处理401,配合防重复刷新和请求重发逻辑;请求拦截器的提前刷新可以作为补充,但一定要做好并发控制。
内容的提问来源于stack exchange,提问作者aylin
相关产品推荐
相关产品推荐

