JWT过期后如何自动刷新Token而不让用户重新登录?

长孙景景 阅读 33

我在React项目里用JWT做用户认证,登录后把access_token和refresh_token都存localStorage了。但access_token只有15分钟有效期,过期后接口就401了,总不能让用户手动重新登录吧?试过在拦截器里判断401就调刷新接口,但经常出现多个请求同时触发刷新,导致拿到多个新token甚至覆盖问题。

下面是我现在写的axios拦截器逻辑,感觉哪里不对:

axios.interceptors.response.use(
  res => res,
  async err => {
    if (err.response?.status === 401) {
      const { data } = await axios.post('/auth/refresh', {
        refreshToken: localStorage.getItem('refresh_token')
      });
      localStorage.setItem('access_token', data.access_token);
      // 重新发起原请求?
      return axios(err.config);
    }
    return Promise.reject(err);
  }
);
我来解答 赞 5 收藏
二维码
手机扫码查看
1 条解答
闲人俊郝
问题的核心在于多个并发请求同时触发token刷新时会产生冲突。我们需要引入一个标志位来控制刷新操作的唯一性,并且在刷新成功后需要重新发起原始请求。

这里给你一套完整的解决方案,一步一步来说明:

首先,我们定义一个正在刷新的状态变量 isRefreshing 和一个队列来保存需要重试的请求:
let isRefreshing = false;
let failedQueue = [];

const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};


接下来修改你的拦截器逻辑,在401错误时先检查是否已经在刷新中:
axios.interceptors.response.use(
res => res,
async err => {
const originalRequest = err.config;

// 如果是401并且没有正在刷新
if (err.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// 正在刷新,加入队列等待
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then(token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token;
return axios(originalRequest);
})
.catch(err => Promise.reject(err));
}

// 标记正在刷新
originalRequest._retry = true;
isRefreshing = true;

try {
const { data } = await axios.post('/auth/refresh', {
refreshToken: localStorage.getItem('refresh_token')
});

localStorage.setItem('access_token', data.access_token);

// 更新所有待处理请求
axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.access_token;
processQueue(null, data.access_token);

// 设置刷新完成
isRefreshing = false;

// 重新发起原请求
originalRequest.headers['Authorization'] = 'Bearer ' + data.access_token;
return axios(originalRequest);
} catch (error) {
processQueue(error, null);
return Promise.reject(error);
}
}
return Promise.reject(err);
}
);


原理是这样的:当检测到401错误时,如果已经有刷新正在进行,我们就把当前请求放入队列等待处理。只有第一次遇到401错误时才会实际去调用刷新接口。这样就避免了多次同时刷新的问题。

还要注意一点,在你的登录逻辑里最好也设置这个默认header:
// 登录成功后设置默认header
axios.defaults.headers.common['Authorization'] = 'Bearer ' + access_token_from_login;


最后提醒一下,别忘了在用户退出登录时清理localStorage和移除这些拦截器。这方案虽然看起来有点复杂,但实际应用中非常可靠,解决了我之前很多项目中的token刷新问题。
点赞
2026-03-30 13:00