使用Axios拦截器刷新JWT Access Token时陷入无限请求循环的解决方法
解决Axios拦截器结合Refresh Token的无限循环问题
核心问题分析
- 拦截器重复注册:每次调用
useAxios都会新增请求拦截器,导致同一请求被多次拦截处理,触发循环。 - Token值未实时更新:
accessToken是Hook初始化时读取的固定值,即便刷新了新Token,判断过期仍用旧值,导致一直判定Token过期。 - 未更新当前请求的Token:刷新Token后未修改当前请求的
Authorization头,请求仍携带旧Token,再次触发过期判断。 - 无并发请求防重机制:多个请求同时触发Token过期时,会重复调用刷新接口,引发不必要的请求甚至循环。
修正后的代码实现
import axios from 'axios'; import jwtDecode from 'jwt-decode'; // 全局标记是否正在刷新Token,避免并发请求重复触发刷新 let isRefreshing = false; // 存储等待Token刷新后重试的请求队列 let requestQueue = []; const useAxios = () => { const axiosInstance = axios.create({ baseURL: process.env.REACT_APP_BASE_URL, withCredentials: true }); // 确保拦截器仅注册一次,避免重复堆积 if (!axiosInstance.interceptors.request.handlers.length) { axiosInstance.interceptors.request.use(async (req) => { // 实时读取最新的accessToken let accessToken = localStorage.getItem("accessToken"); const refreshToken = getCookie("refreshToken"); const email = localStorage.getItem("email"); if (!accessToken) return req; // 用最新Token判断是否过期 const decodedToken = jwtDecode(accessToken); const expired = Date.now() >= decodedToken.exp * 1000; if (!expired) { // 给请求挂载最新的Authorization头 req.headers.Authorization = accessToken; return req; } // 若正在刷新Token,将当前请求加入队列等待 if (isRefreshing) { return new Promise((resolve) => { requestQueue.push(() => { req.headers.Authorization = localStorage.getItem("accessToken"); resolve(req); }); }); } isRefreshing = true; try { const res = await axios.get( `${process.env.REACT_APP_BASE_URL}api/auth/re-issue/${email}`, { headers: { Refresh: refreshToken, }, withCredentials: true } ); const newAccessToken = res.headers.get("Authorization"); localStorage.setItem("accessToken", newAccessToken); // 更新当前请求的Token头 req.headers.Authorization = newAccessToken; // 重试队列中所有等待的请求 requestQueue.forEach(callback => callback()); requestQueue = []; } catch (e) { console.error("刷新Token失败:", e); // 刷新失败时清除本地Token并跳转登录页,终止循环 localStorage.removeItem("accessToken"); window.location.href = "/login"; } finally { isRefreshing = false; } return req; }); } return axiosInstance; }; export default useAxios;
关键优化点说明
- 拦截器防重复注册:通过判断
axiosInstance.interceptors.request.handlers.length,确保拦截器仅初始化一次。 - 实时获取Token:在拦截器内部实时读取localStorage中的accessToken,保证过期判断的准确性。
- 并发请求处理:用
isRefreshing标记和requestQueue队列,避免多个请求同时触发Token刷新,刷新完成后统一重试队列请求。 - 更新当前请求头:刷新Token后立即修改当前请求的
Authorization头,避免携带旧Token再次触发过期逻辑。 - 错误处理优化:刷新Token失败时清除本地Token并跳转登录页,从根源终止循环。
内容的提问来源于stack exchange,提问作者questionmark
相关产品推荐
相关产品推荐

