通用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
相关产品推荐
相关产品推荐

