使用jwt.verify验证令牌出错:Bearer null与401未授权问题
问题现象
登录成功后刷新页面,访问/profile接口返回401 Unauthorized状态码;查看请求头发现Authorization: "Bearer null",后端无法获取JWT令牌完成验证。
相关代码片段
后端登录接口(Node.js/Express)
app.post('/login', async (req, res) => { try { const { email, password } = req.body; // 检查用户是否存在 const user = await User.findOne({ email }); if (!user) { return res.json({ error: '未找到用户', }); } // 验证密码 const match = await comparePasswords(password, user.password); if (match) { jwt.sign( {email: user.email, name: user.name}, process.env.JWT_SECRET, {expiresIn: 86400}, (err, token) => { if (err) { console.error("生成JWT错误:", err); return res.status(500).json({ error: "服务器内部错误" }); } res.cookie('token', token).json({ user }); }); } } catch (err) { console.log(err); } });
后端/profile接口
app.get('/profile', (req, res) => { // 从Cookie获取JWT令牌 const { token } = req.cookies; jwt.verify(token, process.env.JWT_SECRET, {}, (err, user) => { if (err) throw err; res.json(user); }) });
前端UserContext.tsx
import Axios from "axios"; import { createContext, useState, useEffect } from "react"; export const UserContext = createContext({ user: null }); interface UserContextProviderProps { children: JSX.Element | JSX.Element[]; } export function UserContextProvider({ children }: UserContextProviderProps) { const [user, setUser] = useState(null); useEffect(() => { if (user === null) { Axios.get("/profile") .then(({ data }) => { setUser(data); }) .catch((error) => { console.error("获取用户信息失败:", error); }); } }, []); return ( <UserContext.Provider value={{ user }}>{children}</UserContext.Provider> ); }
前端Login.tsx
const [data, setData] = useState({ email: "", password: "", }); const navigateTo = useNavigate(); const handleLogin = async (ev: any) => { ev.preventDefault(); const { email, password } = data; try { const { data } = await Axios.post("/login", { email, password, headers: { Authorization: "Bearer " + localStorage.getItem("token"), "Content-Type": "application/json", }, }); if (data.error) { toast.error(data.error); } else { setData({ email: "", password: "" }); toast.success("登录成功"); navigateTo("/"); } } catch (err) { console.log(err); } };
问题根源分析
- Login.tsx多余的Authorization头:登录时
localStorage.getItem("token")返回null,导致请求头出现无效的Authorization: "Bearer null",且登录接口根本不需要该头。 - Axios未配置跨域携带Cookie:后端将token存在Cookie中,但前端Axios默认不会携带Cookie(跨域场景下更明显),导致
/profile接口无法从req.cookies获取到token。 - 后端/profile接口错误处理不当:token不存在或验证失败时直接
throw err,会导致服务器崩溃,无法正确返回401响应。 - Cookie属性配置缺失:后端设置Cookie时未指定
httpOnly、sameSite等属性,可能导致浏览器无法正确存储或携带Cookie。
修复方案
1. 修复Login.tsx
移除登录请求中不必要的Authorization头:
const handleLogin = async (ev: any) => { ev.preventDefault(); const { email, password } = data; try { const { data } = await Axios.post("/login", { email, password, headers: { "Content-Type": "application/json", }, }); if (data.error) { toast.error(data.error); } else { setData({ email: "", password: "" }); toast.success("登录成功"); navigateTo("/"); } } catch (err) { console.log(err); } };
2. 配置Axios携带Cookie
在项目入口文件或UserContext中全局配置Axios的withCredentials:
// 全局配置,确保所有请求携带Cookie Axios.defaults.withCredentials = true;
或单独在/profile请求中设置:
Axios.get("/profile", { withCredentials: true }) .then(({ data }) => { setUser(data); }) .catch((error) => { console.error("获取用户信息失败:", error); });
3. 修复后端/profile接口错误处理
替换throw err为返回401响应,避免服务器崩溃:
app.get('/profile', (req, res) => { const { token } = req.cookies; if (!token) { return res.status(401).json({ error: '未提供令牌' }); } jwt.verify(token, process.env.JWT_SECRET, {}, (err, user) => { if (err) { return res.status(401).json({ error: '令牌无效或已过期' }); } res.json(user); }) });
4. 优化后端Cookie配置
添加安全属性,确保浏览器正确处理Cookie:
// 登录接口中设置Cookie时补充属性 res.cookie('token', token, { httpOnly: true, // 防止XSS攻击,前端无法通过JS访问Cookie secure: process.env.NODE_ENV === 'production', // 生产环境仅通过HTTPS传输 sameSite: 'strict', // 防范CSRF攻击 maxAge: 86400 * 1000 // 过期时间与JWT保持一致 }).json({ user });
内容的提问来源于stack exchange,提问作者CiMoran
相关产品推荐
相关产品推荐

