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

API返回403状态时如何刷新并更新Redux store中存储的refresh token

实现Refresh Token自动刷新的修改方案

前置准备

  • 后端已提供refresh_token刷新接口,支持传入旧refresh token返回新的access token和refresh token
  • redux的authReducer中已存储refreshToken字段
  • loginActions中已新增updateTokenaction,用于更新redux中存储的token和refreshToken

完整修改后代码

// General api to access data
import ApiConstants from './ApiConstants';
import {store} from '../redux/store';
import * as loginActions from '@/redux/actions/loginActions';

// 全局控制变量:避免重复刷新token、缓存刷新期间的失败请求
let isRefreshing = false;
let failedRequestQueue = [];

export default function api(path, params, method, extraHeaders) {
  let options = {
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      device: 'mobile',
      Authorization: 'Bearer ' + store.getState()?.authReducer?.token,
      ...extraHeaders,
    },
    method: method,
    ...(params && {body: JSON.stringify(params)}),
  };

  return fetch(ApiConstants.BASE_URL + path, options)
    .then(response => {
      return new Promise(function (resolve, reject) {
        response.json().then(responseParsed => {
          if (response.status == 200 || response.status == 201 || response.status == 204) {
            // 成功直接返回
            resolve(responseParsed);
          } else if (response.status == 403) {
            const originalRequestConfig = { path, params, method, extraHeaders };
            // 没有正在刷新的请求时,才触发刷新逻辑
            if (!isRefreshing) {
              isRefreshing = true;
              const currentRefreshToken = store.getState()?.authReducer?.refreshToken;
              
              // 调用刷新token接口,以下路径、参数、判断条件请根据你的后端实际逻辑调整
              fetch(ApiConstants.BASE_URL + '/api/refresh-token', {
                method: 'POST',
                headers: {
                  Accept: 'application/json',
                  'Content-Type': 'application/json',
                  device: 'mobile'
                },
                body: JSON.stringify({ refresh_token: currentRefreshToken })
              })
              .then(res => res.json())
              .then(refreshResult => {
                // 假设刷新成功的状态为code=0,根据实际调整
                if (refreshResult.code === 0) {
                  const newToken = refreshResult.data.access_token;
                  const newRefreshToken = refreshResult.data.refresh_token;
                  // 更新redux中的token
                  store.dispatch(loginActions.updateToken({
                    token: newToken,
                    refreshToken: newRefreshToken
                  }));
                  // 重放所有缓存的失败请求
                  failedRequestQueue.forEach(callback => callback(newToken));
                  failedRequestQueue = [];
                  // 重发当前403的请求
                  resolve(api(path, params, method, extraHeaders));
                } else {
                  // 刷新失败,refresh token也过期,走登出逻辑
                  if (store.getState()?.authReducer?.token) {
                    toast.show('Session Expired. Please Login again', {
                      type: 'danger',
                      position: 'bottom',
                      duration: 3000,
                      offset: 30,
                      animationType: 'zoom-in',
                    });
                  }
                  store.dispatch(loginActions.logOut());
                  resolve({status: false, result: responseParsed});
                }
              })
              .catch(() => {
                // 刷新接口调用异常,走登出逻辑
                if (store.getState()?.authReducer?.token) {
                  toast.show('Session Expired. Please Login again', {
                    type: 'danger',
                    position: 'bottom',
                    duration: 3000,
                    offset: 30,
                    animationType: 'zoom-in',
                  });
                }
                store.dispatch(loginActions.logOut());
                resolve({status: false, result: responseParsed});
              })
              .finally(() => {
                isRefreshing = false;
              })
            } else {
              // 正在刷新token,把当前请求加入缓存队列,等刷新完成后自动重发
              return new Promise(resolve => {
                failedRequestQueue.push((newToken) => {
                  const updatedHeaders = {
                    ...extraHeaders,
                    Authorization: `Bearer ${newToken}`
                  };
                  resolve(api(path, params, method, updatedHeaders));
                })
              })
            }
          } else {
            toast.show(responseParsed?.message, {
              type: 'danger',
              position: 'bottom',
              duration: 3000,
              offset: 30,
              animationType: 'zoom-in',
            });
            resolve({status: false, result: responseParsed});
          }
        });
      });
    })
    .catch(err => {
      console.log('error at utility in function apiCall at catch: ' + err);
    });
}

核心逻辑说明

  • 通过isRefreshing全局锁避免多个请求同时返回403时,重复调用刷新接口的问题
  • 通过failedRequestQueue缓存刷新token期间触发的403请求,刷新成功后统一重放,用户无感知
  • 只有当refresh token也过期、或者刷新接口调用失败时,才会触发登出逻辑,提示用户重新登录
  • 所有调整参数(刷新接口路径、成功状态码、返回字段名)请根据你的后端实际接口规则修改

内容的提问来源于stack exchange,提问作者FortuneCookie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 21:57:04