Axios拦截器刷新Token前弹出401错误的问题排查
Axios拦截器Token刷新失效:401错误提前触发catch块
我尝试用Axios拦截器实现Token自动刷新,预期当Access Token过期时,调用点赞/取消点赞接口会先触发刷新Token接口,获取新Token后重发原请求,实现静默处理。但实际情况是客户端的catch块先捕获到401错误并弹出提示,导致应用异常,拦截器的刷新逻辑反而在错误提示之后才执行。
相关代码
AxiosConfig.js
import axios from "axios"; import { store } from "./redux/store"; import { login, logout } from "./redux/slices/user"; const baseURL = process.env.NEXT_PUBLIC_API_URL; axios.defaults.baseURL = baseURL; export const axiosInstance = axios.create({ withCredentials: true, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", accept: "application/json", }, }); axiosInstance.interceptors.request.use( (config) => { return config; }, (error) => { return Promise.reject(error); } ); axiosInstance.interceptors.response.use( (response) => { return response; }, async (error) => { const originalRequest = error?.config; if (error?.response?.status === 401 && !originalRequest.sent) { originalRequest.sent = true; const response = await axios.get("/auth/refresh", { withCredentials: true, }); if (response.error.status === 403) { store.dispatch(logout()); } store.dispatch(login({ user: response.data.user })); return axiosInstance(originalRequest); } return Promise.reject(error); } );
VerifyJwt.js
export const verifyToken = (req, res, next) => { try { const accessToken = req.cookies.accessToken; if (!accessToken) return res.status(403).json({ message: "Not authenticated, no token provided", }); jwt.verify(accessToken, process.env.ACCESS_SECRET_KEY, (err, decoded) => { if (err) return res.status(401).json({ message: "Not logged in, invalid token", }); req.user = decoded.id; next(); }); } catch (e) { res .status(500) .json({ message: "Something went wrong while verifying jwt" }); } };
routes/video.js
import express from "express"; import { unAuthVerify, verifyToken } from "../middlewares/verifyJwt.js"; import { deleteVideo, dislikeVideo, getVideoDetails, getVideos, likeVideo, updateVideo, uploadVideo, } from "../controllers/videos.js"; const router = express.Router(); router.get("/", getVideos); router.post("/", verifyToken, uploadVideo); router.get("/:id", unAuthVerify, getVideoDetails); router.put("/:id", verifyToken, updateVideo); router.delete("/:id", verifyToken, deleteVideo); router.put("/like/:id", verifyToken, likeVideo); router.put("/dislike/:id", verifyToken, dislikeVideo); export default router;
RefreshToken.js
export const refreshToken = async (req, res) => { const refreshToken = req.cookies.refreshToken; try { if (refreshToken) { jwt.verify( refreshToken, process.env.REFRESH_SECRET_KEY, async (err, decoded) => { if (err) { return res.status(403).json({ message: "Invalid token/not logged in", }); } const accessToken = jwt.sign( { id: decoded.id }, process.env.ACCESS_SECRET_KEY, { expiresIn: "1m", } ); const foundUser = await db.query( "SELECT * FROM channels WHERE id = $1", [decoded.id] ); if (foundUser.rows.length <= 0) { return res.status(400).json({ message: "User/Channel not found", }); } const { password, ...others } = foundUser.rows[0]; if (foundUser.rows.length > 0) { res.cookie("accessToken", accessToken, { httpOnly: true, secure: true, }); return res.status(200).json({ user: others, }); } } ); } else { return res.status(403).json({ message: "Invalid token/not logged in", }); } } catch (e) { console.log(e); res.status(500).json({ message: "Something went wrong.Please try again", }); } };
Video.jsx
const handleLike = async (id) => { if (!isLoggedIn) { toast.error("You need to login to like a video"); return; } if (likedByMe) { toast.success("You have already liked this video"); return; } try { await axiosInstance.put(`/videos/like/${id}`); setLikes((prev) => prev + 1); if (likedByMe === false) { setDisLikes((prev) => prev - 1); } setLikedByMe(true); } catch (e) { console.log("Something went wrong while liking video", e); toast.error(e.response.data.message); // 这里先捕获到401错误弹出提示,之后拦截器才调用刷新接口 } };
问题原因分析
- 刷新请求未使用配置好的axios实例:拦截器中用全局
axios发送刷新请求,而非axiosInstance,导致请求配置不统一,可能刷新请求本身失败,无法获取新Token。 - 错误处理逻辑错误:拦截器中直接访问
response.error.status是错误的,刷新请求成功时response没有error属性,应该用try/catch捕获刷新请求的异常。 - 未标记请求重试状态:原代码用
sent标记,但逻辑不严谨,可能导致重复触发刷新请求。
解决方案
1. 修正Axios拦截器代码
axiosInstance.interceptors.response.use( (response) => { return response; }, async (error) => { const originalRequest = error?.config; // 只处理401且未标记为重试的请求 if (error?.response?.status === 401 && originalRequest && !originalRequest._retry) { originalRequest._retry = true; // 标记为已重试,避免循环请求 try { // 使用同一个axiosInstance发送刷新请求 const refreshResponse = await axiosInstance.get("/auth/refresh"); // 更新store中的用户信息 store.dispatch(login({ user: refreshResponse.data.user })); // 重发原请求 return axiosInstance(originalRequest); } catch (refreshError) { // 刷新Token失败,触发登出 if (refreshError.response?.status === 403) { store.dispatch(logout()); toast.error("登录已过期,请重新登录"); } // 抛出刷新错误,让上层处理 return Promise.reject(refreshError); } } // 非401错误或已重试的请求,直接抛出 return Promise.reject(error); } );
2. 优化上层catch块的错误提示
在handleLike的catch块中,避免重复弹出401错误:
catch (e) { // 仅当不是401错误,或刷新已失败时才弹出提示 if (e.response?.status !== 401 || e.response.data.message === "登录已过期,请重新登录") { console.log("Something went wrong while liking video", e); toast.error(e.response?.data?.message || "操作失败"); } }
内容的提问来源于stack exchange,提问作者Milan Poudel
相关产品推荐
相关产品推荐

