React应用中如何在Axios请求拦截器自动刷新Access Token?
在Axios请求拦截器中实现Token过期自动刷新逻辑
核心思路
- 解析Access Token的过期时间,判断是否即将/已经过期
- 若Token过期,调用Redux Toolkit中的刷新Token方法更新Store中的Access Token
- 使用新Token重新配置请求头,继续发起原请求
- 处理并发请求场景,避免重复触发Token刷新
实现步骤
1. 添加Token过期解析工具函数
先实现解析JWT Token过期时间的工具函数:
// 解析JWT Token,获取过期时间戳(毫秒级) const getTokenExpiration = (token: string): number | null => { try { const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp * 1000; } catch (error) { console.error('解析Token失败:', error); return null; } }; // 判断Token是否已过期(提前30秒触发刷新,避免边缘情况) const isTokenExpired = (token: string, threshold = 30000): boolean => { const expTime = getTokenExpiration(token); if (!expTime) return true; return Date.now() >= expTime - threshold; };
2. 处理并发请求的锁机制
添加全局锁和请求队列,防止多个请求同时触发Token刷新:
// 标记是否正在刷新Token let isRefreshing = false; // 存储等待刷新完成的请求回调 let requestQueue: ((token: string) => void)[] = [];
3. 修改Axios请求拦截器
更新你的createAPI函数,加入Token检查和刷新逻辑:
import { store } from './path-to-your-redux-store'; // 导入你的Redux Store import { refreshToken } from './path-to-your-user-slice'; // 导入刷新Token的Thunk Action export const createAPI = (): AxiosInstance => { const api = axios.create({ baseURL: getAPIURL(), timeout: REQUEST_TIMEOUT, withCredentials: true }); api.interceptors.request.use( async (config: InternalAxiosRequestConfig) => { let accessToken = store.getState().user.accessToken; // 检查Token是否过期 if (accessToken && isTokenExpired(accessToken)) { if (!isRefreshing) { isRefreshing = true; try { // 调用Redux Toolkit的刷新Token方法 await store.dispatch(refreshToken()).unwrap(); // 获取刷新后的新Token accessToken = store.getState().user.accessToken; // 执行队列中等待的请求 requestQueue.forEach(callback => callback(accessToken)); requestQueue = []; } catch (error) { console.error('刷新Token失败:', error); // 刷新失败可清空Token并跳转登录页 // store.dispatch(clearUser()); // window.location.href = '/login'; } finally { isRefreshing = false; } } else { // 若正在刷新,将当前请求加入队列等待 await new Promise<string>((resolve) => { requestQueue.push((token) => resolve(token)); }).then(newToken => { accessToken = newToken; }); } } // 给请求头设置最新的Token if (accessToken && config.headers) { config.headers['authorization'] = `Bearer ${accessToken}`; } if (!config.data) return config; config.data = adaptFromClientToServer(config.data); return config; }, (error) => Promise.reject(error) ); api.interceptors.response.use( (response) => { if (!response.data) return response; response.data = adaptFromServerToClient(response.data); return response; }, (error) => Promise.reject(error) ); return api; };
4. 确保Redux Toolkit的refreshToken Action正确实现
你的User Slice中需要包含刷新Token的异步Thunk,示例如下:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; interface UserState { accessToken: string | null; refreshToken: string | null; } const initialState: UserState = { accessToken: localStorage.getItem('accessToken') || null, refreshToken: localStorage.getItem('refreshToken') || null, }; // 刷新Token的异步Thunk export const refreshToken = createAsyncThunk( 'user/refreshToken', async (_, { rejectWithValue, getState }) => { const state = getState() as { user: UserState }; const refreshToken = state.user.refreshToken; if (!refreshToken) { return rejectWithValue('无可用的Refresh Token'); } try { const response = await axios.post('/api/auth/refresh', { refreshToken }); const { accessToken: newAccessToken, refreshToken: newRefreshToken } = response.data; // 保存到本地存储(可选) localStorage.setItem('accessToken', newAccessToken); localStorage.setItem('refreshToken', newRefreshToken); return { accessToken: newAccessToken, refreshToken: newRefreshToken }; } catch (error) { return rejectWithValue('刷新Token失败'); } } ); const userSlice = createSlice({ name: 'user', initialState, reducers: {}, extraReducers: (builder) => { builder.addCase(refreshToken.fulfilled, (state, action) => { state.accessToken = action.payload.accessToken; state.refreshToken = action.payload.refreshToken; }); builder.addCase(refreshToken.rejected, (state) => { // 刷新失败,清空Token state.accessToken = null; state.refreshToken = null; localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); }); }, }); export default userSlice.reducer;
关键注意事项
- 并发请求处理:通过
isRefreshing锁和请求队列,确保多个并发请求只会触发一次Token刷新,所有等待请求都能使用新Token发起。 - 过期阈值设置:提前30秒触发刷新,避免Token刚好在请求发送时过期导致接口报错。
- 错误兜底:刷新Token失败时,要清空本地和Store中的Token,引导用户重新登录。
内容的提问来源于stack exchange,提问作者Eujenio Gonzalez
相关产品推荐
相关产品推荐

