NodeJS JWT登录后私有路由报错:Cannot set headers after they are sent to the client
问题
开发在线医疗咨询应用后端时,JWT认证遇到异常:
项目包含/register、/login、/appointments三个路由,/register和/login可正常访问,但用/login生成的JWT Token放入Authorization请求头访问/appointments私有路由时,抛出如下错误:
node:internal/errors:484 ErrorCaptureStackTrace(err); ^ Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at new NodeError (node:internal/errors:393:5) at ServerResponse.setHeader (node:_http_outgoing:644:11) at ServerResponse.header (C:\Users\krish\Desktop\mrcooper-task\server\node_modules\express\lib\response.js:794:10) at ServerResponse.send (C:\Users\krish\Desktop\mrcooper-task\server\node_modules\express\lib\response.js:174:12) at module.exports.login (C:\Users\krish\Desktop\mrcooper-task\server\controllers\authController.js:62:7) { code: 'ERR_HTTP_HEADERS_SENT' } Node.js v18.12.0
奇怪的是,重启服务器后用同一个Token能正常访问/appointments路由。已知该错误通常是单次请求发送多个响应导致,但检查代码后未发现明显多响应情况,疑惑是否存在条件分支中的响应泄漏,想知道重启前报错的原因。
相关代码
index.js
const express = require("express"); const cors = require("cors"); const mongoose = require("mongoose"); const app = express(); require('dotenv').config(); //External routes const authRoutes = require("./routes/authRoutes"); const appointRoutes = require("./routes/appointRoutes"); // Middlewares app.use(cors()); app.use(express.json()); //DB connection const dbURI = process.env.DB_URL; mongoose .connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true }) .then((result) => app.listen(8080, () => console.log("App sucessfully started on localhost port 8080") ) ) .catch((err) => console.log(err)); //Internal routes app.use(authRoutes); app.use(appointRoutes);
appointRoutes.js
const { Router } = require("express"); const verify = require("./verifyToken") const router = Router(); router.get("/appointments",verify, (req, res) => { res.send({ message: "Appointment route" }); }); module.exports = router;
authRoutes.js
const { Router } = require("express"); const User = require("../models/User"); const bcrypt = require("bcryptjs"); const jwt = require("jsonwebtoken"); const { registerValidator, loginValidator } = require("../validation"); const router = Router(); router.post("/register", async (req, res) => { //Validate data before creating a user const { error } = registerValidator(req.body); if (error) { return res.status(400).send(error.details[0].message); } //Check if email already exists const emailExists = await User.findOne({ email: req.body.email }); if (emailExists) { return res.status(400).send("Email already exists"); } const { name, email, password, catogery, DOB } = req.body; //Hash password const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); try { const user = await User.create({ name, email, password: hashedPassword, catogery, DOB, }); res.status(201).json({ user: user._id }); } catch (err) { res.status(400).send(err); } }); router.post("/login", async (req, res) => { const { email, password } = req.body; //Validate data before authenticating a user const { error } = loginValidator(req.body); if (error) { return res.status(400).send(error.details[0].message); } //Check if email doesn't exists const user = await User.findOne({ email: req.body.email }); if (!user) { return res .status(400) .send("Email doesn't exists. Please register and try again"); } //Password is incorrect const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) return res.status(400).send("Invalid Password"); //Create and assign JWT token const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET) res.header('auth-token', token).send(token); res.send("Logged In!"); }); module.exports = router;
问题分析与解决
根源:单次请求发送多个响应
错误日志明确指向login方法中的多次响应。看authRoutes.js里的/login路由代码:
res.header('auth-token', token).send(token); res.send("Logged In!");
这里先调用res.send(token)发送了一次完整响应,紧接着又调用res.send("Logged In!")尝试发送第二次响应。HTTP协议要求单次请求只能对应一个响应,第二次尝试发送时,响应头已经发送给客户端,所以触发ERR_HTTP_HEADERS_SENT错误。
为什么重启后现象看似正常?
重启服务器后,你大概率没有重复调用/login接口,而是直接使用之前生成的有效Token访问/appointments——此时/login路由没有被触发,自然不会出现多次响应的错误,所以/appointments能正常访问。本质上不是重启修复了问题,只是避开了触发错误的场景。
修复方案
修改/login路由,确保单次请求只发送一次响应。可以选择合并信息或者保留单一响应:
// 方案1:同时返回Token和登录提示 const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET) res.header('auth-token', token).send({ message: "Logged In!", token: token }); // 方案2:仅返回Token(兼容原有客户端逻辑) const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET) res.header('auth-token', token).send(token);
内容的提问来源于stack exchange,提问作者Krishna
相关产品推荐
相关产品推荐

