React Native+Redux实现OAuth令牌自动刷新的技术方案咨询
嘿,这个场景我之前做React Native项目的时候刚好碰到过,折腾了一阵终于找到比较流畅的实现方式,分享给你参考下~
实现思路与步骤
核心就是把令牌的有效性校验、自动刷新、状态更新这一套逻辑封装成可复用的工具,让所有API请求都自动走这个流程,不用每个action creator都重复写token处理代码,完全贴合Redux的单向数据流规则。
1. 先在Redux里定义令牌相关状态
首先得在Store里存好必要的令牌信息,用Redux Toolkit写个slice会很方便:
// src/features/auth/authSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { accessToken: null, refreshToken: null, tokenExpiry: null, // 存储accessToken的过期时间戳(比如 new Date().getTime() + expiresIn*1000) isRefreshingToken: false, // 标记是否正在刷新token,避免并发请求重复触发刷新 }; const authSlice = createSlice({ name: 'auth', initialState, reducers: { setTokens: (state, action) => { const { accessToken, refreshToken, expiresIn } = action.payload; state.accessToken = accessToken; state.refreshToken = refreshToken; state.tokenExpiry = new Date().getTime() + expiresIn * 1000; }, setRefreshingStatus: (state, action) => { state.isRefreshingToken = action.payload; }, clearTokens: (state) => { Object.assign(state, initialState); }, }, }); export const { setTokens, setRefreshingStatus, clearTokens } = authSlice.actions; export default authSlice.reducer;
2. 封装令牌刷新的核心Thunk函数
写一个异步Thunk,专门负责获取有效accessToken,内部自动处理过期判断和刷新逻辑:
// src/features/auth/authThunks.js import { createAsyncThunk } from '@reduxjs/toolkit'; import { setTokens, setRefreshingStatus, clearTokens } from './authSlice'; import apiClient from '../../utils/apiClient'; // 提前30秒就视为即将过期,提前触发刷新 const EXPIRY_BUFFER = 30 * 1000; export const getValidAccessToken = createAsyncThunk( 'auth/getValidAccessToken', async (_, { getState, dispatch }) => { const { auth } = getState(); const { accessToken, tokenExpiry, refreshToken, isRefreshingToken } = auth; // 如果正在刷新token,稍等一下再返回最新token if (isRefreshingToken) { await new Promise(resolve => setTimeout(resolve, 500)); return getState().auth.accessToken; } // 检查token是否有效且未过期 if (accessToken && tokenExpiry && new Date().getTime() + EXPIRY_BUFFER < tokenExpiry) { return accessToken; } // 没有refreshToken直接清空状态跳登录 if (!refreshToken) { dispatch(clearTokens()); throw new Error('请重新登录'); } try { dispatch(setRefreshingStatus(true)); // 调用后端刷新token接口 const res = await apiClient.post('/auth/refresh', { refreshToken }); const { accessToken: newAT, refreshToken: newRT, expiresIn } = res.data; // 更新Store里的令牌信息 dispatch(setTokens({ accessToken: newAT, refreshToken: newRT, expiresIn })); return newAT; } catch (err) { // 刷新失败,清空令牌跳登录 dispatch(clearTokens()); throw err; } finally { dispatch(setRefreshingStatus(false)); } } );
3. 封装全局API请求拦截器
把所有API请求都通过这个封装好的客户端发送,自动帮你处理token的添加和刷新:
// src/utils/apiClient.js import axios from 'axios'; import { store } from '../store'; // 你的Redux Store实例 import { getValidAccessToken } from '../features/auth/authThunks'; const apiClient = axios.create({ baseURL: 'https://your-api-domain.com', }); // 请求拦截器:自动添加有效token到请求头 apiClient.interceptors.request.use( async (config) => { const accessToken = await store.dispatch(getValidAccessToken()).unwrap(); if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } return config; }, (err) => Promise.reject(err) ); // 响应拦截器:处理突发401的情况(比如token突然失效) apiClient.interceptors.response.use( (res) => res, async (err) => { if (err.response?.status === 401) { try { // 尝试重新刷新token并重发请求 const newAT = await store.dispatch(getValidAccessToken()).unwrap(); err.config.headers.Authorization = `Bearer ${newAT}`; return apiClient(err.config); } catch (refreshErr) { // 刷新失败,跳登录页(这里用你项目的导航方式,比如React Navigation) // navigationRef.navigate('Login'); return Promise.reject(refreshErr); } } return Promise.reject(err); } ); export default apiClient;
4. 在业务Action Creator里直接用封装好的API
现在你的业务代码完全不用关心token的问题,直接调用apiClient就行:
// src/features/user/userThunks.js import { createAsyncThunk } from '@reduxjs/toolkit'; import apiClient from '../../utils/apiClient'; export const fetchUserProfile = createAsyncThunk( 'user/fetchProfile', async () => { const res = await apiClient.get('/user/profile'); return res.data; } );
一些关键注意事项
- 并发请求防重复刷新:通过
isRefreshingToken状态避免多个请求同时触发刷新接口,这个非常重要,不然会导致后端重复生成token。 - 过期时间计算要准确:一定要把后端返回的
expiresIn(秒数)转换成时间戳存在Store里,不要靠猜时间。 - 刷新失败的降级处理:如果refreshToken也失效了,必须清空状态跳登录,避免无限循环刷新。
这样整个流程就完全闭环了,每次发起API请求前都会自动确保token有效,完全符合Redux的单向数据流原则~
内容的提问来源于stack exchange,提问作者BlackLog
相关产品推荐
相关产品推荐

