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
相关产品推荐
相关产品推荐

