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

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


问题分析与修复方案

核心问题点

  1. 拦截器逻辑漏洞:

    • 未处理/token/refresh/接口返回401的情况,导致原请求无限重试报错
    • 多余的token过期判断:收到401时token已无效,dayjs.unix(user.exp).diff(dayjs()) >=1逻辑完全多余且干扰流程
    • axios.post参数错误:将headers和withCredentials拆分为两个config对象,导致请求配置失效
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 15:57:07