前端Refresh-Token返回401错误但Postman正常,求助排查
正在为系统实现Refresh-Token功能,目标是浏览器刷新后保留系统状态,但目前刷新页面会直接跳转到登录页。系统已能正常生成Refresh-Token,但前端调用刷新接口时返回401 Unauthorized错误,使用Postman发送相同请求却能正常运行。
相关代码
AuthController.php
public function refreshToken(Request $request) { if (!$request->user()) { return response()->json(['message' => 'Unauthorized'], 401); } $request->user()->tokens()->delete(); // 吊销用户所有令牌 $remember_token = $request->user()->createToken('remember_token')->plainTextToken; $expiresAt = now()->addMinute(30)->format('Y-m-d H:i:s'); return response()->json([ 'remember_token' => $remember_token, 'expires_at' => $expiresAt ]); }
useRefreshToken Hook(JSX)
import axios from '../api/axios'; import useAuth from './useAuth'; const TOKEN_URL = '/api/refresh-token'; const useRefreshToken = () => { const { auth, setAuth } = useAuth(); // 获取认证状态和设置方法 const refresh = async () => { const remember_token = localStorage.getItem('remember_token'); const config = { headers: { Authorization: `Bearer ${remember_token}` } }; const response = await axios.get( TOKEN_URL, config ) localStorage.setItem('remember_token', response.data.remember_token); setAuth(prev => { console.log(JSON.stringify(prev)); console.log(response.data.remember_token); return { ...prev, role_id: response.data.role_id, remember_token: response.data.remember_token } }); return response.data.remember_token } return refresh; }; export default useRefreshToken;
PersistLogin.jsx
import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Outlet } from "react-router-dom"; import useAuth from '../hooks/useAuth'; import useRefreshToken from '../hooks/useRefreshToken'; const PersistLogin = () => { const [isLoading, setIsLoading] = useState(true); const refresh = useRefreshToken(); const { auth,setAuth } = useAuth(); useEffect(() => { const verifyRefreshToken = async () => { try{ const storedToken = localStorage.getItem('remember_token'); if (storedToken) { setAuth(prev => ({ ...prev, remember_token: storedToken })); await refresh(); // 调用刷新函数更新令牌 } } catch(err){ console.error(err); } finally{ setIsLoading(false); } } !auth?.remember_token ? verifyRefreshToken() : setIsLoading(false); }, []) useEffect(() => { console.log(`isLoading: ${isLoading}`) console.log(`aT: ${JSON.stringify(auth?.remember_token)}`) }, [isLoading]) return ( <> {isLoading ? <div>Loading...</div> : <Outlet/>} </> ) } export default PersistLogin;
排查与解决思路
跨域请求凭证配置
前端axios请求需开启withCredentials: true,浏览器跨域请求时默认不会携带凭证,而Postman不受此限制。检查axios全局配置或请求实例是否添加了该选项。请求头格式校验
确认Authorization头格式是否正确:Bearer后必须有空格,且token无多余空格、换行符。可在前端请求前打印Authorization值,和Postman中的请求头对比。后端认证中间件配置
确保refresh-token路由使用了正确的认证中间件(比如Laravel的auth:sanctum),中间件需能正确解析请求头中的Bearer token。可在后端方法开头添加日志,打印接收到的Authorization头,确认token是否正确传递。LocalStorage有效性检查
刷新页面后,先打印localStorage.getItem('remember_token'),确认存储的token和Postman中使用的一致,排除存储时的截断、格式错误问题。PersistLogin逻辑优化
当前逻辑中setAuth是异步更新状态,但refresh直接读取LocalStorage,逻辑无问题,但可尝试将storedToken作为参数传入refresh函数,避免重复读取LocalStorage,减少潜在的竞态问题。后端token验证逻辑
检查$request->user()为空的原因:是否因为token已过期、格式错误,或者后端验证逻辑未正确解析请求头。可临时注释if (!$request->user())判断,打印token解析结果,定位问题。
内容的提问来源于stack exchange,提问作者Christian Medallada

