Access Token过期后请求返回401,刷新页面才恢复的问题排查
问题描述
- access token过期时,调用
/token/refresh/接口返回401状态码 - 访问
127.0.0.1:8000/home/触发未授权,仅再次刷新页面后可正常访问
相关代码
axios.js
import axios from "axios"; import jwt_decode from 'jwt-decode'; import dayjs from 'dayjs' let refresh = false; axios.defaults.headers.common['Authorization'] = `Bearer ${localStorage.getItem('access_token')?localStorage.getItem('access_token'):null}` let access_token = null; axios.interceptors.response.use(resp => {console.log("success!!!!"); return resp}, async error => { if (error.response.status === 401 && !refresh) { refresh = true; access_token = localStorage.getItem('access_token'); const user = jwt_decode(access_token); if(dayjs.unix(user.exp).diff((dayjs())) >= 1) { axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`; return axios(error.config); } const response = await axios.post('http://127.0.0.1:8000/token/refresh/', {refresh:localStorage.getItem('refresh_token')}, {headers: {'Content-Type': 'application/json'}}, {withCredentials: true} ); access_token = response.data.access; axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`; localStorage.setItem('access_token', response.data.access); localStorage.setItem('refresh_token', response.data.refresh); return axios(error.config); } refresh = false; return error; });
Login.js
import axios from "axios"; import { useState } from "react"; // Define the Login function. export const Login = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [invalid, setInvalid] = useState(false); // Create the submit method. const submit = async (e) => { e.preventDefault(); const user = { username: username, password: password, }; // Create the POST requuest try { const { data } = await axios.post("http://127.0.0.1:8000/token/", user, {headers: {'Content-Type': 'application/json'}}, {withCredentials: true}); // Initialize the access & refresh token in localstorage. localStorage.clear(); localStorage.setItem("access_token", data.access); localStorage.setItem("refresh_token", data.refresh); let access_token = localStorage.getItem('access_token'); axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`; window.location.href = "/"; } catch (e) { setInvalid(true); } }; return ( <div className="Auth-form-container"> <form className="Auth-form" onSubmit={submit}> <div className="Auth-form-content"> <h3 className="Auth-form-title">Sign In</h3> <div className="form-group mt-3"> <label>Username</label> <input className="form-control mt-1" placeholder="Enter Username" name="username" type="text" value={username} required onChange={(e) => setUsername(e.target.value)} /> </div> <div className="form-group mt-3"> <label>Password</label> <input name="password" type="password" className="form-control mt-1" placeholder="Enter password" value={password} required onChange={(e) => setPassword(e.target.value)} /> </div> <div className="d-grid gap-2 mt-3"> <button type="submit" className="btn btn-primary"> Submit </button> </div> </div> </form> {invalid && "Invalid Credentials!!!"} </div> ); };
Home.js
import {useEffect, useState} from "react"; import axios from "axios"; // Define the Login function. export const Home = () => { const [message, setMessage] = useState(''); useEffect(() => { if(localStorage.getItem('access_token') === null){ window.location.href = '/login' } else{ (async () => { try { const {data} = await axios.get( `http://127.0.0.1:8000/home/`, ); setMessage(data.message); } catch (e) { console.log('111'); } })()}; }, []); return ( <div className="form-signin mt-5 text-center"> <h3>Hi {message}</h3> </div> ); }
控制台报错信息(翻译后)
GET http://127.0.0.1:8000/home/ 401 (未授权)
GET http://127.0.0.1:8000/home/ 401 (未授权)
111
success!!!!
GET http://127.0.0.1:8000/home/ 401 (未授权)
GET http://127.0.0.1:8000/home/ 401 (未授权)
111
问题分析与修复方案
核心问题点
拦截器逻辑漏洞:
- 未处理
/token/refresh/接口返回401的情况,导致原请求无限重试报错 - 多余的token过期判断:收到401时token已无效,
dayjs.unix(user.exp).diff(dayjs()) >=1逻辑完全多余且干扰流程 - axios.post参数错误:将headers和withCredentials拆分为两个config对象,导致请求配置失效
- 未处理
Home组件请求逻辑缺陷:
- 组件挂载时直接发起请求,若token刚好过期,拦截器刷新token的过程中,原请求已失败且无重新触发逻辑
修复代码
1. 修正axios拦截器(axios.js)
import axios from "axios"; import jwt_decode from 'jwt-decode'; import dayjs from 'dayjs' let refresh = false; // 初始化请求头时避免生成无效的"Bearer null" const initToken = localStorage.getItem('access_token'); axios.defaults.headers.common['Authorization'] = initToken ? `Bearer ${initToken}` : ''; axios.interceptors.response.use( resp => { console.log("success!!!!"); return resp; }, async error => { // 跳过非401错误或正在刷新的情况 if (error.response?.status !== 401 || refresh) { refresh = false; return Promise.reject(error); } refresh = true; const refreshToken = localStorage.getItem('refresh_token'); try { // 合并请求配置,确保参数正确 const response = await axios.post( 'http://127.0.0.1:8000/token/refresh/', { refresh: refreshToken }, { headers: { 'Content-Type': 'application/json' }, withCredentials: true } ); const newAccessToken = response.data.access; const newRefreshToken = response.data.refresh; // 更新本地存储与全局请求头 localStorage.setItem('access_token', newAccessToken); localStorage.setItem('refresh_token', newRefreshToken); axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`; // 更新原请求的Authorization头后重试 error.config.headers['Authorization'] = `Bearer ${newAccessToken}`; return axios(error.config); } catch (refreshError) { // refresh token过期,清空存储并跳转登录 localStorage.clear(); window.location.href = '/login'; return Promise.reject(refreshError); } finally { refresh = false; } } );
2. 优化Home组件请求逻辑(Home.js)
import { useEffect, useState } from "react"; import axios from "axios"; export const Home = () => { const [message, setMessage] = useState(''); const [isLoading, setIsLoading] = useState(true); const fetchHomeData = async () => { try { const { data } = await axios.get(`http://127.0.0.1:8000/home/`); setMessage(data.message); } catch (e) { console.log('请求失败:', e); } finally { setIsLoading(false); } }; useEffect(() => { const token = localStorage.getItem('access_token'); if (!token) { window.location.href = '/login'; return; } fetchHomeData(); }, []); if (isLoading) { return <div className="form-signin mt-5 text-center">加载中...</div>; } return ( <div className="form-signin mt-5 text-center"> <h3>Hi {message}</h3> </div> ); };
3. 优化Login组件token设置(Login.js)
// ... 其他代码不变 try { const { data } = await axios.post( "http://127.0.0.1:8000/token/", user, { headers: { 'Content-Type': 'application/json' }, withCredentials: true } ); localStorage.clear(); localStorage.setItem("access_token", data.access); localStorage.setItem("refresh_token", data.refresh); // 直接使用返回的token设置请求头,无需从localStorage读取 axios.defaults.headers.common['Authorization'] = `Bearer ${data.access}`; window.location.href = "/"; } catch (e) { setInvalid(true); } // ...
关键修复说明
- 处理refresh接口401的情况:refresh token过期时自动跳转登录,避免循环报错
- 修正axios请求参数格式:合并config对象,确保请求配置生效
- 移除无效的token过期判断:收到401时token已无效,无需额外判断
- 重试请求时更新原请求头:确保重试请求携带新token
- 增加加载状态:提升用户体验,避免页面空白
内容的提问来源于stack exchange,提问作者Vansh_Chaudhary
相关产品推荐
相关产品推荐

