You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

请求前校验Token有效性还是响应后处理?哪种刷新Access Token方式更优?

Token校验与刷新的最佳实践分析

问题1:该在请求前校验Token,还是等响应后再校验?

两种方式各有用处,实际项目里建议两者结合:

  • 请求前校验:
    • 好处:提前拦住明显过期的Token,不用发无效请求给服务器,省资源也能更快给用户反馈。
    • 不足:只能判断Token是否过期,管不了服务器端主动废掉的Token(比如账号被封、Token被拉黑),这种情况还是会收到401。
  • 响应后校验:
    • 好处:是兜底方案,不管服务器因为啥原因判定Token无效(包括本地没检测到的情况),都能处理。
    • 不足:会多一次无效请求,但这点损耗基本可以忽略。

问题2:请求拦截器提前刷新,还是响应拦截器抓401后刷新?

推荐以响应拦截器捕获401为主,请求拦截器校验为辅,原因如下:

请求拦截器提前刷新的坑

  1. 时间差问题:本地时间和服务器时间可能不一样,比如本地觉得Token还能用,但服务器已经判它过期了,还是会返回401;或者本地误判过期,白刷一次Token。
  2. Token有效期不同步:Id Token和Access Token的过期时间未必一致,比如Access Token还没到期,但Id Token先过期了,这时候提前刷新纯属鸡肋;反过来Access Token过期但Id Token没过期,本地校验会漏掉,还是会触发401。
  3. 并发请求重复刷新:多个请求同时触发的话,会反复调用refreshTokens(),造成多余的刷新请求,还得额外加锁控制。

响应拦截器抓401的优势

  1. 准没错:只有服务器明确返回401,才触发刷新,完全跟着服务器的判定走,不会出错。
  2. 逻辑简单:不用纠结本地时间、Token有效期不同步这些破事,只处理服务器给的明确错误就行。
  3. 并发好处理:加个isRefreshing标记就能避免重复刷新,刷新完还能把之前失败的请求都重发一遍,用户体验更好。

你的代码需要调整的地方

现有代码有几个小问题:

  1. 请求拦截器里await refreshTokens()之后,没重新拿最新的accessToken,等于还是用旧Token发请求,白刷了。
  2. 响应拦截器里refreshTokens()没加await,也没重发原请求,用户的操作直接就失败了。
  3. 没处理并发请求重复刷新的情况。

修正后的参考代码:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 03:15:47