刷新令牌过期后如何重定向至登录页?解决React+Axios无限循环问题
问题分析
- Axios无法使用React Router跳转:Axios模块是纯JS文件,无法直接访问React Router的
useNavigate钩子,直接用window.location.href会导致页面刷新,不符合SPA路由跳转逻辑。 - 无限循环崩溃:refresh token过期时,
refreshAccessToken请求失败后跳转登录页,但未清除本地token和Redux登录状态,后续未完成的API请求会持续触发401拦截,重复调用刷新逻辑,最终形成循环导致浏览器崩溃。
解决方案
1. 用Redux同步状态+组件监听实现路由跳转
在Axios拦截器中触发Redux的logout action清除状态,再在App组件中监听登录状态变化,自动完成SPA路由跳转。
修改axios.js文件
import axios from "axios"; import { store } from "./redux/store"; // 导入Redux store实例 import { logout } from "./redux/actions/authActions"; const BASE_URL = "http://localhost:8083/api/v1"; const axiosInstance = axios.create({ baseURL: BASE_URL, }); const refreshAccessToken = async () => { try { const refresh_token = localStorage.getItem("refresh_token"); // 用原生axios发起刷新请求,避免触发自定义拦截器导致循环 const response = await axios.get(`${BASE_URL}/auth/refresh`, { headers: { Authorization: `Bearer ${refresh_token}`, }, }); const access_token = response.data.access_token; localStorage.setItem("access_token", access_token); return access_token; } catch (error) { // 清除本地存储的所有token localStorage.removeItem("access_token"); localStorage.removeItem("refresh_token"); // 触发logout action更新全局登录状态 store.dispatch(logout()); return Promise.reject(error); } }; axiosInstance.interceptors.request.use( async (config) => { if (!config.headers.Authorization) { const access_token = localStorage.getItem("access_token"); if (access_token) { config.headers = config.headers || {}; config.headers.Authorization = `Bearer ${access_token}`; } } return config; }, (error) => Promise.reject(error) ); axiosInstance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error?.config; // 仅处理非刷新请求的401,且确保同一请求只重试一次 if ( error.response?.status === 401 && !originalRequest?.sent && originalRequest?.url !== "/auth/refresh" ) { originalRequest.sent = true; try { const new_access_token = await refreshAccessToken(); originalRequest.headers.Authorization = `Bearer ${new_access_token}`; return axiosInstance(originalRequest); } catch (refreshError) { // 刷新失败直接拒绝原请求,不再重试 return Promise.reject(refreshError); } } // 处理刷新请求本身的401 if (error.response?.status === 401 && originalRequest?.url === "/auth/refresh") { localStorage.removeItem("access_token"); localStorage.removeItem("refresh_token"); store.dispatch(logout()); } return Promise.reject(error); } ); export default axiosInstance;
修改App.jsx文件
import Login from "./components/Login"; import Register from "./components/Register"; import Topics from "./components/Topics"; import Root from "./components/Root"; import Dashboard from "./components/dashboard"; import { Routes, Route, useNavigate, Navigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { login, logout } from "./redux/actions/authActions"; import React, { useEffect } from "react"; import EmployeeList from "./components/EmployeeList"; function App() { const isLoggedIn = useSelector((state) => state.auth.isLoggedIn); const dispatch = useDispatch(); const navigate = useNavigate(); // 监听登录状态,未登录时自动跳转到登录页 useEffect(() => { if (!isLoggedIn) { navigate("/", { replace: true }); } }, [isLoggedIn, navigate]); const handleLogin = () => { dispatch(login()); }; const handleLogout = () => { dispatch(logout()); }; return ( <Routes> {/* 未登录显示登录页,已登录则跳转到主页 */} <Route path="/" element={!isLoggedIn ? <Login onLogin={handleLogin} /> : <Navigate to="/topics" replace />} /> {/* 已登录时的路由组 */} {isLoggedIn && ( <Route path="/" element={<Root onLogout={handleLogout} />}> <Route index element={<Topics />} /> <Route path="employeesList" element={<EmployeeList />} /> <Route path="register" element={<Register />} /> <Route path="dashboard" element={<Dashboard />} /> </Route> )} {/* 兜底路由重定向 */} <Route path="*" element={isLoggedIn ? <Navigate to="/" replace /> : <Navigate to="/" replace />} /> </Routes> ); } export default App;
2. 关键优化点说明
- 避免拦截器循环:刷新token的请求使用原生
axios,防止触发自定义拦截器;同时排除/auth/refresh路径的401处理逻辑。 - 状态全局同步:refresh失败时清除本地token并触发Redux的
logoutaction,确保所有组件同步感知登录状态变化。 - SPA友好跳转:通过
useEffect监听登录状态变化,结合useNavigate实现无刷新路由跳转,替代window.location.href的页面刷新方式。 - 防止重复重试:给原请求添加
sent标记,确保同一请求只触发一次刷新逻辑,避免重复请求导致的循环。
3. 额外建议
- 登录组件成功登录后,要正确存储
access_token和refresh_token到本地存储,并触发loginaction更新Redux状态。 - 可添加请求取消逻辑,当用户注销或token过期时,取消所有pending请求,进一步减少不必要的拦截触发。
内容的提问来源于stack exchange,提问作者raio
相关产品推荐
相关产品推荐

