ApolloClient实现refreshToken时出现无限循环问题求助
问题根因
- 死循环核心原因:
refreshTokens使用Apollo Client发起的查询请求会再次经过authMiddleware中间件,触发token过期校验,进而重复调用refreshTokens,形成自循环。 - 刷新逻辑缺陷:你当前没有把refreshToken携带到刷新请求的请求头中,后端
issueNewToken拿不到refreshToken会直接抛错,刷新始终失败,旧的过期token一直留在缓存中,每次校验都会触发刷新。 - 状态未更新:就算刷新成功,你注释了
accessTokenVar的更新代码,新token没有写入缓存,校验逻辑永远判断token已过期。 - 无并发控制:如果同一时间有多个请求触发过期校验,会同时发起多个刷新请求,进一步加剧循环问题。
修复方案
1. 补充缓存变量
在cache.js中新增refreshToken的响应式存储变量:
import { makeVar } from "@apollo/client"; // 原有变量不变 export const refreshTokenVar = makeVar(null);
登录调用createOrGetUser成功后,需要把返回的token和refreshToken分别写入accessTokenVar和refreshTokenVar。
2. 改造App.js代码
首先补充必要导入:
import { Observable } from '@apollo/client/utilities';
添加刷新锁和请求队列逻辑,避免并发刷新:
let isRefreshing = false; let pendingRequests = []; // 刷新完成后批量执行排队的请求 const resolvePendingRequests = () => { pendingRequests.forEach(callback => callback()); pendingRequests = []; };
改造refreshTokens方法,刷新成功后更新缓存:
const refreshTokens = () => { return client.query({ query: REFRESH_TOKENS, fetchPolicy: 'no-cache' // 禁用缓存,强制发起新请求 }).then((response) => { const { token, refreshToken } = response.data.refreshTokens; accessTokenVar(token); refreshTokenVar(refreshToken); return { token, refreshToken }; }); };
重写authMiddleware,添加刷新请求白名单、等待刷新逻辑:
const authMiddleware = new ApolloLink((operation, forward) => { let token = accessTokenVar(); const operationName = operation.query.definitions[0].name?.value; // 刷新token的请求直接跳过过期检查,携带refreshToken到请求头 if (operationName === 'refreshTokens') { const refreshToken = refreshTokenVar(); operation.setContext(({ headers = {} }) => ({ headers: { ...headers, refreshtoken: refreshToken || "" } })); return forward(operation); } if (token) { const pureToken = token.replace("Bearer ", ""); const { exp } = jwtDecode(pureToken); const expirationTime = exp - 30; if (Date.now() / 1000 >= expirationTime) { if (!isRefreshing) { isRefreshing = true; refreshTokens() .then(() => resolvePendingRequests()) .catch(() => { // 刷新失败清空登录状态,跳转到登录页 accessTokenVar(null); refreshTokenVar(null); isLoggedInVar(false); }) .finally(() => isRefreshing = false); } // 把当前请求加入队列,等刷新完成后再执行 return new Observable(observer => { pendingRequests.push(() => { const newToken = accessTokenVar(); operation.setContext(({ headers = {} }) => ({ headers: { ...headers, authorization: newToken ? newToken.replace("Bearer ", "") : "" } })); forward(operation).subscribe(observer); }); }); } } operation.setContext(({ headers = {} }) => ({ headers: { ...headers, authorization: token ? token.replace("Bearer ", "") : "", } })); return forward(operation); });
3. 后端补充异常兜底
修改Auth辅助函数的issueNewToken方法,无refreshToken时主动抛错:
issueNewToken: async (req) => { try { const token = req.headers.refreshtoken; if (!token) { throw new AuthenticationError("Missing Refresh Token."); } // 原有逻辑保持不变 const decoded = await jwt.verify(token, jwtRefreshTokenSecret); let user = await User.findById(decoded.id); if (!user) { throw new AuthenticationError("No user found."); } let tokens = await authFunctions.issueToken(user); return { ...tokens, user }; } catch (err) { throw new AuthenticationError("Invalid Refresh Token."); } },
内容的提问来源于stack exchange,提问作者Ruham
相关产品推荐
相关产品推荐

