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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 21:07:29