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

通用fetch重放实现方案:处理401场景下的token自动刷新

fetch 401自动刷新令牌并重放请求通用实现方案

核心解决思路

整个方案不需要修改现有业务代码,完全对齐原生fetch的参数和返回值,对前端开发者透明,核心解决三个关键问题:

  • 原始请求的可重放克隆
  • 并发场景下的刷新令牌防重复调用
  • 避免循环调用、无限重试的边界处理

具体实现代码

首先保存原生fetch的引用,避免重写后出现循环调用,再通过全局锁控制刷新令牌的并发逻辑:

// 留存原生fetch引用
const nativeFetch = window.fetch;
// 全局刷新锁,存储正在进行的刷新令牌Promise
let pendingRefresh = null;

window.fetch = async function(input, init) {
  // 统一将入参转为Request实例,提前克隆,避免请求body流被消费后无法重放
  const originalReq = new Request(input, init);
  // 标记是否已经过重试,防止无限重放
  const hasRetried = originalReq.headers.get('X-Fetch-Retried') === '1';

  // 发起首次请求
  let resp = await nativeFetch(originalReq.clone());

  // 仅拦截未重试过的401请求(令牌过期场景)
  if (resp.status === 401 && !hasRetried) {
    // 无正在进行的刷新任务时,发起令牌刷新
    if (!pendingRefresh) {
      pendingRefresh = (async () => {
        try {
          const refreshToken = getRefreshToken();
          // 调用原生fetch发起刷新请求,绕过拦截逻辑避免死循环
          const tokenResp = await nativeFetch('https://idaas.provider/get/new/token', {
            method: 'POST',
            body: new URLSearchParams({
              grant_type: 'refresh_token',
              refresh_token: refreshToken,
              client_id: client_id_str
            })
          });
          const tokenData = await tokenResp.json();
          // 将新token同步给负责注入鉴权头的ServiceWorker
          send_new_token_to_service_worker(tokenData.new_token);
          return tokenData.new_token;
        } finally {
          // 无论刷新成功/失败,都清空锁,允许后续触发新的刷新流程
          pendingRefresh = null;
        }
      })();
    }

    // 等待令牌刷新完成
    await pendingRefresh;

    // 构造重放请求,加上重试标记
    const retryReq = new Request(originalReq.clone(), {
      headers: new Headers({
        ...Object.fromEntries(originalReq.headers.entries()),
        'X-Fetch-Retried': '1'
      })
    });
    // 重放请求,ServiceWorker会自动注入新的access_token
    resp = await nativeFetch(retryReq);
  }

  // 返回最终响应,行为与原生fetch完全一致
  return resp;
};

关键逻辑说明

  • 请求克隆:fetch的Request对象携带的body是可读流,一旦被请求消费就无法再次读取,必须在首次发起请求前通过new Request()或者clone()方法复制独立的请求实例,才能支持后续重放。
  • 并发锁设计:如果页面同时发起多个请求遇到token过期,没有锁的情况下会并发触发多次刷新令牌请求,而大部分OIDC服务会在刷新成功后作废旧refresh_token,导致后续刷新请求失败。加锁后所有并发401请求会共享同一次刷新结果,不会重复调用刷新接口。
  • 死循环规避:刷新令牌时直接调用留存的原生fetch,不走重写后的拦截逻辑;同时给重放请求加重试标记,即使重放后依然返回401(比如refresh_token也过期),也不会无限重试,直接把错误响应返回给业务层即可。
  • 兼容性:重写后的fetch参数、返回值和原生完全一致,存量业务代码不需要任何修改,开发者依然按照原生写法调用即可,整个鉴权刷新流程完全在底层自动执行。

执行流程

实现后底层自动执行的流程完全符合预期:
[初始请求] > [令牌过期返回401] > [调用refresh_token换取新token] > [同步新token到ServiceWorker] > [重放原始请求] > [返回最终响应给业务代码]


内容的提问来源于stack exchange,提问作者user1913559

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:12:19