React Native刷新令牌(Refresh Token)机制异常排查及解决方案咨询
这个问题我之前也踩过类似的坑,核心原因就是刷新Token的请求已经被后端处理完毕(生成了新的Refresh Token并存库),但APP在收到响应前被关闭,导致本地还没更新成新的Refresh Token。下次启动时用旧的Refresh Token去请求,后端自然不认,直接返回400错误。
下面给你几个可行的解决思路,结合你的现有代码来修改:
1. 增加「刷新中」状态的持久化,启动时自动校验
在发起Refresh Token请求前,把「正在刷新」的状态存到本地存储;刷新成功/失败后,再把这个状态置为false。APP启动时先检查这个状态,如果是true,说明上次可能有未完成的刷新流程,主动做一次校验修复:
第一步:重构TokenStorage工具类
把所有Token相关信息(包括刷新状态)存在同一个JSON对象里,保证更新是原子性的,避免部分更新的问题:
// TokenStorage.js 示例 import AsyncStorage from '@react-native-async-storage/async-storage'; export default { async storeTokenInfo(access_token, refresh_token, userName) { const tokenData = { accessToken: access_token, refreshToken: refresh_token, userName, isRefreshing: false // 标记是否处于刷新流程中 }; await AsyncStorage.setItem('app_token_info', JSON.stringify(tokenData)); }, async getToken() { const data = await AsyncStorage.getItem('app_token_info'); return data ? JSON.parse(data).accessToken : null; }, async getRefreshToken() { const data = await AsyncStorage.getItem('app_token_info'); return data ? JSON.parse(data).refreshToken : null; }, async getUsername() { const data = await AsyncStorage.getItem('app_token_info'); return data ? JSON.parse(data).userName : null; }, async setIsRefreshing(isRefreshing) { const data = await AsyncStorage.getItem('app_token_info'); if (data) { const tokenData = JSON.parse(data); tokenData.isRefreshing = isRefreshing; await AsyncStorage.setItem('app_token_info', JSON.stringify(tokenData)); } }, async getIsRefreshing() { const data = await AsyncStorage.getItem('app_token_info'); return data ? JSON.parse(data).isRefreshing : false; } };
第二步:修改刷新Token的核心逻辑
在resetTokenAndReattemptRequest函数里,发起刷新请求前设置状态,完成后重置:
async function resetTokenAndReattemptRequest(error) { try { const { response: errorResponse } = error; const resetToken = await TokenStorage.getToken(); if (!resetToken) { return Promise.reject(error); } const retryOriginalRequest = new Promise(resolve => { addSubscriber(accesstoken => { errorResponse.config.headers.Authorization = 'Bearer ' + accesstoken; resolve(axios(errorResponse.config)); }); }); if (!isAlreadyFetchingAccessToken) { isAlreadyFetchingAccessToken = true; // 1. 标记刷新状态到本地 await TokenStorage.setIsRefreshing(true); const refreshToken = await TokenStorage.getRefreshToken(); const userName = await TokenStorage.getUsername(); const response = await AuthenticationService.refreshToken(refreshToken, userName); if (!response) { await TokenStorage.setIsRefreshing(false); // 失败也要重置状态 return Promise.reject(error); } // 2. 保存新的Token信息(自动把isRefreshing设为false) await TokenStorage.storeTokenInfo( response.access_token, response.refresh_token, response.userName, ); isAlreadyFetchingAccessToken = false; onAccessTokenFetched(response.access_token); } return retryOriginalRequest; } catch (err) { isAlreadyFetchingAccessToken = false; await TokenStorage.setIsRefreshing(false); // 异常时重置状态 store.dispatch(signOut({ relogin: true, notServerLogout: true })); return Promise.reject(err); } }
第三步:APP启动时自动校验状态
在App的根组件(比如App.js)里,启动时检查「是否有未完成的刷新」:
import { useEffect } from 'react'; import TokenStorage from './path/to/TokenStorage'; import AuthenticationService from './path/to/AuthenticationService'; import store from './path/to/store'; import { signOut } from './path/to/authActions'; function App() { useEffect(() => { const checkPendingRefresh = async () => { const isRefreshing = await TokenStorage.getIsRefreshing(); const refreshToken = await TokenStorage.getRefreshToken(); const userName = await TokenStorage.getUsername(); if (isRefreshing && refreshToken && userName) { try { // 尝试用旧的Refresh Token再次请求,验证是否有效 const response = await AuthenticationService.refreshToken(refreshToken, userName); // 如果成功,说明上次刷新其实完成了,只是状态没更新,直接保存新Token await TokenStorage.storeTokenInfo( response.access_token, response.refresh_token, response.userName ); } catch (err) { // 失败说明旧的Refresh Token已经失效,直接引导登录 await TokenStorage.setIsRefreshing(false); store.dispatch(signOut({ relogin: true, notServerLogout: true })); } } }; checkPendingRefresh(); }, []); // ...其他组件代码 } export default App;
2. 监听APP生命周期,在后台时处理刷新请求
通过RN的AppState监听APP切换到后台的事件,如果此时正在刷新Token,主动标记状态,避免异常:
// 在根组件的useEffect里添加 import { AppState } from 'react-native'; useEffect(() => { const handleAppStateChange = async (nextState) => { // 当APP切换到后台或 inactive 状态时 if (['background', 'inactive'].includes(nextState) && isAlreadyFetchingAccessToken) { // 把刷新状态持久化,下次启动处理 await TokenStorage.setIsRefreshing(true); } }; AppState.addEventListener('change', handleAppStateChange); return () => { AppState.removeEventListener('change', handleAppStateChange); }; }, []);
3. 后端协同优化(可选)
如果前端调整后仍有小概率问题,可以和后端同学商量:
- 允许旧的Refresh Token在刷新后的短时间内(比如30秒)仍然有效,给客户端足够的更新缓冲期
- 刷新Token时,返回新Token的同时,不立即失效旧的,直到新Token被成功使用一次
这样即使客户端没及时更新,也不会直接触发400错误。
内容的提问来源于stack exchange,提问作者mohsinali1317
相关产品推荐
相关产品推荐

