使用JWT与Express.js实现认证失败及重定向异常问题
我正在开发一个Web应用的认证模块,使用运行在Docker容器中的MongoDB实例,后端采用Node.js和Express.js。
登录接口代码
app.post("/api/auth/signin", async (req, res) => { const username = req.body.username; const password = req.body.password; const userRequested = await db.collection("users").findOne({username:username}); if (userRequested == null) { res.status(401).json({ message: "Requested user does not exist", }); res.json(username); } else if (username == null || password == null) { res.status(401).json({ message: "Null username or password are not allowed", }); } else { if (userRequested.password === password) { jwt.sign({ user: userRequested }, "secretkey", (err, token) => { res.redirect("/api/budget/whoami"); }); } else { res.status(401).send({ message: "Wrong credentials", }); } } });
当用户输入正确凭证时,会重定向至/api/budget/whoami。
身份验证接口代码
app.get("/api/budget/whoami", (req, res, next) => { //res.json(req.headers["authorization"]); const bearerHeader = req.headers["authorization"]; if (typeof bearerHeader !== undefined) { const bearerToken = bearerHeader.split(" ")[1]; req.token = bearerToken; next(); } else { res.sendStatus(403); res.json(req.headers["authorization"]) } }, (req, res) => { jwt.verify(req.token, "secretkey", (err, authData) => { if (err) { res.sendStatus(403); } else { res.status(201); res.json({ authData }); } }); } );
测试表单代码
<html> <head> <title>Ajax</title> <script src="assets/js/app.js" defer></script> <link rel="stylesheet" href="assets/css/style.css"/> </head> <body> <form id="loginForm" action="/api/auth/signin" method="post"> <label for="username">Username:</label> <input type="text" name="username" id="username" /> <label for="password">Password:</label> <input type="password" name="password" id="password" /> <input type="submit" value="login" /> </form> </body> </html>
问题
即使输入正确凭证,重定向后的响应仍是403 Forbidden,希望重定向页面能返回包含用户信息的JSON文档,无法理解该错误。
补充说明:已将"undefined"改为undefined,并使用了相同的签名密钥,但现在提交凭证后重定向未执行,页面无限加载。
核心问题1:重定向未携带JWT Token
你在登录接口生成了JWT Token,但直接用res.redirect跳转时,浏览器的重定向请求不会自动带上这个Token。而/api/budget/whoami接口需要从请求头的authorization字段获取Token,自然拿不到,就返回403。而且你现在的代码里,生成Token的回调函数只做了重定向,根本没把Token传给前端——浏览器完全不知道Token存在,更别说带到重定向请求里了。
核心问题2:登录接口逻辑顺序错误
你先判断userRequested == null,之后才判断username == null || password == null。如果用户没输入用户名或密码,userRequested会是null,直接触发第一个判断返回401,第二个判断永远不会执行,逻辑顺序搞反了。
核心问题3:多次响应导致无限加载
比如在第一个判断里,你先res.status(401).json(...),又调用res.json(username)——一个请求只能有一次响应,多次调用会导致Express内部出错,这也是页面无限加载的原因之一。
解决步骤
- 调整登录接口逻辑顺序,返回Token而非重定向
app.post("/api/auth/signin", async (req, res) => { const username = req.body.username; const password = req.body.password; // 先判断用户名/密码是否为空 if (!username || !password) { return res.status(401).json({ message: "Null username or password are not allowed", }); } const userRequested = await db.collection("users").findOne({username: username}); if (!userRequested) { return res.status(401).json({ message: "Requested user does not exist", }); } if (userRequested.password === password) { jwt.sign({ user: userRequested }, "secretkey", (err, token) => { if (err) { return res.status(500).json({message: "Token generation failed"}); } // 返回Token给前端,由前端处理后续请求 res.json({ token: token, redirectUrl: "/api/budget/whoami" }); }); } else { return res.status(401).json({ message: "Wrong credentials", }); } });
- 修改前端表单提交逻辑,手动携带Token请求接口
// assets/js/app.js document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); // 阻止表单默认跳转 const username = document.getElementById('username').value; const password = document.getElementById('password').value; const response = await fetch('/api/auth/signin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({username, password}) }); const data = await response.json(); if (data.token) { // 存储Token到本地 localStorage.setItem('authToken', data.token); // 手动请求whoami接口并在请求头带上Token const whoamiResponse = await fetch('/api/budget/whoami', { headers: { 'Authorization': `Bearer ${data.token}` } }); const userData = await whoamiResponse.json(); console.log(userData); // 也可以跳转到指定页面展示用户信息 // window.location.href = '/user-profile'; } else { alert(data.message); } });
- 修复whoami接口的响应问题
app.get("/api/budget/whoami", (req, res, next) => { const bearerHeader = req.headers["authorization"]; if (typeof bearerHeader !== 'undefined') { const bearerToken = bearerHeader.split(" ")[1]; req.token = bearerToken; next(); } else { // 只返回一次响应 return res.sendStatus(403); } }, (req, res) => { jwt.verify(req.token, "secretkey", (err, authData) => { if (err) { return res.sendStatus(403); } else { res.status(200).json({ authData }); // 201用于创建资源,这里用200更合适 } }); } );
为什么不能用重定向?
重定向是浏览器发起的新GET请求,没办法自动把登录接口生成的Token带到请求头里。正确的流程应该是:登录接口返回Token → 前端保存Token → 后续请求手动在请求头里带上Token。
内容的提问来源于stack exchange,提问作者Eminent Emperor Penguin

