React Expo中2分钟过期Token的最优刷新方案咨询
Token刷新优化方案
你当前用固定时长的setTimeout刷新Token确实存在不少问题:比如Token实际有效期可能和你设定的2分钟不符、网络波动时刷新请求失败会导致后续请求报错、多次触发useEffect可能创建多个定时器冲突。下面是几种更可靠的优化方案:
方案一:基于Token过期时间动态计算刷新时机
大部分JWT Token里会包含exp字段(过期时间戳),我们可以解析这个字段,在Token即将过期前(比如提前30秒)触发刷新,比固定时间更精准。
实现步骤:
- 写一个解析Token获取过期时间的函数
- 在Token更新后,计算剩余有效期,设置对应的定时器
- 清理旧定时器,避免内存泄漏
示例代码:
// 解析JWT Token,获取过期时间 const getTokenExpiration = (token) => { if (!token) return 0; const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp * 1000; // 转成毫秒 }; React.useEffect(() => { let refreshTimer; if (token) { const expTime = getTokenExpiration(token); // 提前30秒刷新 const refreshDelay = expTime - Date.now() - 30000; if (refreshDelay > 0) { refreshTimer = setTimeout(() => { refreshCurrentToken(); }, refreshDelay); } } // 组件卸载或token变化时清理定时器 return () => clearTimeout(refreshTimer); }, [token]); // 刷新函数去掉定时器参数,简化逻辑 async function refreshCurrentToken() { try { const res = await axios.post("https://login.iee.ihu.gr/token", { grant_type: "refresh_token", code: refresh, client_id: cID, client_secret: cSecret, }); setToken(res.data.access_token); setRefresh(res.data.refresh_token); } catch (err) { console.log(err); // 刷新失败可跳转登录或其他降级逻辑 // navigate('/login'); } }
方案二:请求拦截器触发刷新(推荐)
不用定时轮询,而是在每次发起请求前检查Token状态,即将过期或已过期时先刷新Token,再继续请求。这种方式更高效,避免无意义的定时请求。
实现步骤:
- 给axios添加请求拦截器
- 拦截器里检查Token是否即将过期
- 若需要刷新,先发起刷新请求,更新Token后再继续原请求
- 加锁防止同时触发多次刷新请求
示例代码:
// 全局刷新锁,防止重复刷新 let isRefreshing = false; // 存储等待刷新完成后再发起的请求 let requestsQueue = []; // 创建axios实例 const api = axios.create({ baseURL: '你的API地址' }); // 请求拦截器 api.interceptors.request.use(async (config) => { const currentToken = token; if (!currentToken) { // 无Token直接跳转登录 // navigate('/login'); return Promise.reject(new Error('无有效Token')); } const expTime = getTokenExpiration(currentToken); // 距离过期不足30秒,触发刷新 if (expTime - Date.now() < 30000) { if (!isRefreshing) { isRefreshing = true; try { const res = await axios.post("https://login.iee.ihu.gr/token", { grant_type: "refresh_token", code: refresh, client_id: cID, client_secret: cSecret, }); const newToken = res.data.access_token; setToken(newToken); setRefresh(res.data.refresh_token); // 更新当前请求的Token config.headers.Authorization = `Bearer ${newToken}`; // 执行队列里的请求 requestsQueue.forEach(cb => cb(newToken)); requestsQueue = []; } catch (err) { console.log(err); // 刷新失败跳转登录 // navigate('/login'); requestsQueue.forEach(cb => cb(Promise.reject(err))); requestsQueue = []; } finally { isRefreshing = false; } } else { // 正在刷新,把当前请求加入队列 return new Promise((resolve, reject) => { requestsQueue.push((newToken) => { if (newToken) { config.headers.Authorization = `Bearer ${newToken}`; resolve(config); } else { reject(new Error('Token刷新失败')); } }); }); } } else { // Token有效,直接设置请求头 config.headers.Authorization = `Bearer ${currentToken}`; } return config; }, (error) => { return Promise.reject(error); }); // 解析Token函数同上 const getTokenExpiration = (token) => { if (!token) return 0; const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp * 1000; };
方案对比
- 动态定时刷新:适合需要保持Token持续有效(比如实时推送场景),但仍存在刷新请求失败的风险,需要处理失败后的降级逻辑。
- 请求拦截器刷新:更高效,只在需要请求时处理,避免无用请求,是大部分业务场景的首选。
另外注意:
- 刷新Token的接口本身要确保可靠,最好加上重试机制。
- 刷新失败后要及时引导用户重新登录,避免无限循环刷新。
内容的提问来源于stack exchange,提问作者kostis14
相关产品推荐
相关产品推荐

