React中使用axios-auth-refresh实现刷新令牌时部分API在401场景下不重试的问题求助
我仔细看了你的代码和问题描述,发现几个关键问题导致fetchUsers返回401时不触发重试,下面一步步拆解原因和修复方案:
1. 最直接的错误:Axios请求参数传递格式错误
Axios的get方法只接受两个参数:url和config(可选),所有配置(包括params、validateStatus)都要放在同一个config对象里。你现在的写法把params和validateStatus分开传递,导致validateStatus根本没被Axios识别,请求的错误处理逻辑不符合预期。
修复后的fetchUsers:
const fetchUsers = async () => { try { const { data } = await api.get(`users/`, { params: { tripUsers: true }, validateStatus: (status) => status !== 401 && status !== 403 }) setUsers(data); } catch (err) { console.error("获取用户列表失败:", err); } }
修复后的fetchProfile(同样修正参数格式):
const fetchProfile = async () => { try { const { data } = await api.get(`/users/${user.userId}`, { validateStatus: (status) => status !== 401 && status !== 403 }) const {statusCode, message} = data; console.log(data); if(!statusCode) { setState(data); } } catch (err) { console.error("获取用户信息失败:", err); } }
2. Refresh逻辑的错误处理依赖错误
你的refreshAuthLogic里判断token是否无效,依赖的是返回数据中的statusCode,而不是Axios响应的实际状态码。如果后端直接返回401/403状态码,而数据里没有statusCode字段,这段判断就会失效,导致后续逻辑出错,无法触发重试。
修复后的refreshAuthLogic:
const refreshAuthLogic = async (failedRequest) => { const refresh_token = Cookies.get(REFRESH_TOKEN_COOKIE_NAME); if (!refresh_token) { // 没有刷新令牌,直接拒绝,不重试 return Promise.reject(new Error("无可用刷新令牌")); } try { const tokenRefreshResponse = await api.post('auth/createtoken', {}, { headers: {'Authorization': 'Bearer ' + refresh_token}, // 只接受成功或明确的错误状态码,不用强制所有状态都视为成功 validateStatus: (status) => status === 200 || status === 401 || status === 403 }); // 用响应状态码判断是否失效,更可靠 if (tokenRefreshResponse.status === 401 || tokenRefreshResponse.status === 403) { Cookies.remove(REFRESH_TOKEN_COOKIE_NAME); if (!window.location.href.includes('login')) { window.location.href = "http://localhost:3000/login"; } return Promise.reject(new Error("刷新令牌无效")); } const access_token = tokenRefreshResponse.data.access_token; if (!access_token) throw new Error("未返回新的访问令牌"); Cookies.set(TOKEN_COOKIE_NAME, access_token, { expires: 60 }) api.defaults.headers.Authorization = `Bearer ${access_token}`; failedRequest.response.config.headers['Authorization'] = `Bearer ${access_token}`; // 返回成功Promise,告诉axios-auth-refresh可以重试请求 return Promise.resolve(); } catch (err) { // 处理网络错误或其他异常 Cookies.remove(REFRESH_TOKEN_COOKIE_NAME); if (!window.location.href.includes('login')) { window.location.href = "http://localhost:3000/login"; } return Promise.reject(err); } }
3. 缺失初始Authorization头初始化
页面刷新后,你的Axios实例默认没有从Cookie中读取已有的访问令牌,导致第一次请求(比如fetchUsers)没有携带Authorization头,后端返回401时,刷新逻辑的触发可能出现异常。
在api.js中添加初始化逻辑:
const api = Axios.create({ baseURL: process.env.REACT_APP_BACKEND_URL, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); // 新增:初始化Authorization头 const initialAccessToken = Cookies.get(TOKEN_COOKIE_NAME); if (initialAccessToken) { api.defaults.headers.Authorization = `Bearer ${initialAccessToken}`; } // 后续的refresh拦截器代码不变...
4. 可选:添加拦截器调试配置
可以给axios-auth-refresh添加调试选项,方便排查重试触发情况:
createAuthRefreshInterceptor(api, refreshAuthLogic, { statusCodes: [401, 403], // 指定触发刷新的状态码 onRetry: (request) => { console.log("正在重试请求:", request.url); } });
为什么fetchProfile之前能正常触发?
因为fetchProfile的第二个参数是空对象,Axios把它当成了config,第三个参数的validateStatus被忽略了,请求用了默认的错误处理逻辑(401视为错误),所以触发了刷新拦截器。而fetchUsers的参数格式错误,但因为是页面加载后的第一个请求,没有初始Authorization头,加上刷新逻辑的判断问题,导致重试未触发。
按照上面的步骤修复后,fetchUsers返回401时应该能正常触发令牌刷新和请求重试了。
内容的提问来源于stack exchange,提问作者Saro

