Node.js登录场景触发ERR_HTTP_HEADERS_SENT错误,求代码排查方案
登录接口ERR_HTTP_HEADERS_SENT错误排查与解决
问题场景
输入错误用户名或密码后,再使用正确凭证登录,触发以下错误:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at new NodeError (node:internal/errors:371:5) at ServerResponse.setHeader (node:_http_outgoing:576:11) at ServerResponse.header (D:\ecom website\ecom backend\node_modules\express\lib\response.js:794:10) at ServerResponse.send (D:\ecom website\ecom backend\node_modules\express\lib\response.js:174:12) at ServerResponse.json (D:\ecom website\ecom backend\node_modules\express\lib\response.js:278:15) at D:\ecom website\ecom backend\routes\auth.js:57:21 at processTicksAndRejections (node:internal/process/task_queues:96:5) { code: 'ERR_HTTP_HEADERS_SENT' } [nodemon] app crashed - waiting for file changes before starting...
登录接口代码如下:
//LOGIN router.post('/login', async (req, res) => { try { const user = await User.findOne({ username: req.body.username, }); !user && res.status(401).json('Wrong User Name'); const hashedPassword = CryptoJS.AES.decrypt( user.password, process.env.PASS_SEC ); const originalPassword = hashedPassword.toString(CryptoJS.enc.Utf8); const inputPassword = req.body.password; originalPassword != inputPassword && res.status(401).json('Wrong Password'); const accessToken = jwt.sign( { id: user._id, isAdmin: user.isAdmin, }, process.env.JWT_SEC, { expiresIn: '3d' } ); const { password, ...others } = user._doc; res.status(200).json({ ...others, accessToken }); } catch (err) { res.status(500).json(err); } });
问题原因
代码存在多次响应客户端请求的逻辑漏洞:
- 当用户名错误时,执行
!user && res.status(401).json('Wrong User Name')发送错误响应,但代码不会终止,后续仍会继续执行解密密码、生成Token等逻辑,最终触发res.status(200).json(...)再次发送响应,导致重复设置HTTP头。 - 密码错误时同理,发送错误响应后代码继续执行到成功响应步骤,造成同一请求被多次响应。
HTTP协议要求一个请求只能对应一个响应,多次调用res.json()/res.send()会直接触发ERR_HTTP_HEADERS_SENT错误。
解决方法
在发送错误响应后,通过return终止函数执行,避免后续代码继续运行:
修改后的代码:
//LOGIN router.post('/login', async (req, res) => { try { const user = await User.findOne({ username: req.body.username, }); // 用户名错误时返回并终止函数 if (!user) { return res.status(401).json('Wrong User Name'); } const hashedPassword = CryptoJS.AES.decrypt( user.password, process.env.PASS_SEC ); const originalPassword = hashedPassword.toString(CryptoJS.enc.Utf8); const inputPassword = req.body.password; // 密码错误时返回并终止函数 if (originalPassword !== inputPassword) { return res.status(401).json('Wrong Password'); } const accessToken = jwt.sign( { id: user._id, isAdmin: user.isAdmin, }, process.env.JWT_SEC, { expiresIn: '3d' } ); const { password, ...others } = user._doc; res.status(200).json({ ...others, accessToken }); } catch (err) { res.status(500).json(err); } });
关键修改点
- 将原短路逻辑
!user && ...改为if (!user) { return ... },确保发送错误响应后直接终止函数,不再执行后续代码。 - 密码校验逻辑同理,用
if判断加return,保证一次请求只发送一次响应。
内容的提问来源于stack exchange,提问作者Vlad Bocai
相关产品推荐
相关产品推荐

