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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:35:37