查看: 166|回复: 3

Express请求验证中间件与express-validator实践

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
Express中间件的标准签名是function middlewareName(req, res, next)。验证中间件在这个函数中读取req.body等请求数据,检查字段是否存在、类型和格式是否符合要求。验证失败时,用res.status(400).json(...)返回错误,并且不要调用next;验证通过时,调用next()把控制权交给后续处理器。原文强调两个关键点:验证通过却忘记调用next,请求会挂起,客户端收不到响应;验证失败后仍调用next,后续业务逻辑可能继续执行。因此验证中间件应放在业务处理器之前,并通常在express.json()之后,以便访问解析后的req.body。

一、手写验证中间件

以用户注册接口为例,需要确保username和email存在且格式正确。可以创建独立模块封装验证逻辑,代码来自原文示例:
  1. // middleware/validateUser.js
  2. const validateUser = (req, res, next) => {
  3.   const { username, email } = req.body;
  4.   const errors = [];
  5.   // 验证用户名
  6.   if (!username || typeof username !== 'string' || username.trim().length < 3) {
  7.     errors.push('Username is required and must be at least 3 characters long');
  8.   }
  9.   // 验证邮箱格式
  10.   const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  11.   if (!email || !emailRegex.test(email)) {
  12.     errors.push('Valid email address is required');
  13.   }
  14.   // 如果有错误,返回 400 状态码并停止后续执行
  15.   if (errors.length > 0) {
  16.     return res.status(400).json({
  17.       status: 'error',
  18.       message: 'Validation failed',
  19.       details: errors
  20.     });
  21.   }
  22.   // 验证通过,继续执行下一个中间件或路由处理器
  23.   next();
  24. };
  25. module.exports = validateUser;
复制代码

该中间件先用errors数组收集多个字段的错误,再统一返回400响应。username要求为字符串且trim后长度不少于3,email用正则测试。只有errors为空时才调用next。

挂载到路由时,中间件顺序很关键:先解析JSON请求体,再执行验证中间件,最后进入业务处理器。示例:
  1. const express = require('express');
  2. const app = express();
  3. const validateUser = require('./middleware/validateUser');
  4. // 解析 JSON 请求体
  5. app.use(express.json());
  6. // POST /register 路由
  7. // 顺序:1. parse JSON -> 2. validateUser -> 3. handler
  8. app.post('/register', validateUser, (req, res) => {
  9.   // 此时可以确信 req.body.username 和 req.body.email 是有效的
  10.   const { username, email } = req.body;
  11.   // 模拟数据库操作
  12.   console.log(`Creating user: ${username}, ${email}`);
  13.   res.status(201).json({
  14.     status: 'success',
  15.     message: 'User registered successfully',
  16.     data: { username, email }
  17.   });
  18. });
  19. app.listen(3000, () => console.log('Server running on port 3000'));
复制代码

这样写适合字段较少、规则简单的场景。优点是不依赖额外库,缺点是复杂项目里容易堆积正则和条件判断,维护成本会上升。

二、express-validator链式验证

原文推荐在复杂项目中使用express-validator。它基于validator.js,提供链式调用验证规则和数据净化功能。安装命令:
  1. npm install express-validator
复制代码

注册接口可以定义验证规则数组,并用validationResult(req)读取结果。示例代码:
  1. const { body, validationResult } = require('express-validator');
  2. const express = require('express');
  3. const app = express();
  4. app.use(express.json());
  5. // 定义验证规则数组
  6. const registerValidationRules = [
  7.   body('username')
  8.     .trim()
  9.     .notEmpty().withMessage('Username is required')
  10.     .isLength({ min: 3 }).withMessage('Username must be at least 3 characters'),
  11.   body('email')
  12.     .normalizeEmail()
  13.     .isEmail().withMessage('Invalid email format'),
  14.   body('password')
  15.     .isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
  16.     .matches(/[0-9]/).withMessage('Password must contain a number')
  17. ];
  18. // 通用错误处理中间件(可选,用于统一处理验证结果)
  19. const handleValidationErrors = (req, res, next) => {
  20.   const errors = validationResult(req);
  21.   if (!errors.isEmpty()) {
  22.     return res.status(400).json({ errors: errors.array() });
  23.   }
  24.   next();
  25. };
  26. // 应用验证规则
  27. app.post('/register',
  28.   registerValidationRules, // 1. 执行验证规则
  29.   handleValidationErrors,  // 2. 检查验证结果
  30.   (req, res) => {
  31.     // 3. 业务逻辑
  32.     res.json({ message: 'User created', data: req.body });
  33.   }
  34. );
  35. app.listen(3000);
复制代码

这里的body('username')、body('email')、body('password')分别针对字段建立规则。trim()和normalizeEmail()属于原文提到的数据净化能力,notEmpty()、isLength()、isEmail()、matches()负责格式和完整性校验。handleValidationErrors把validationResult(req)的结果统一转成400响应,避免在每个路由里重复写错误检查。

三、可复用的验证工厂

如果多个路由都要执行“运行规则、检查结果、失败返回400”的流程,可以用工厂函数生成中间件。原文示例:
  1. const { body, validationResult } = require('express-validator');
  2. // 创建一个通用的验证中间件生成器
  3. const createValidator = (validations) => {
  4.   return async (req, res, next) => {
  5.     // 执行所有传入的验证规则
  6.     await Promise.all(validations.map(validation => validation.run(req)));
  7.     const errors = validationResult(req);
  8.     if (errors.isEmpty()) {
  9.       return next();
  10.     }
  11.     res.status(400).json({ errors: errors.array() });
  12.   };
  13. };
  14. // 定义具体的验证规则
  15. const userRegistrationValidations = [
  16.   body('email').isEmail(),
  17.   body('password').isLength({ min: 6 })
  18. ];
  19. // 生成中间件
  20. const validateRegistration = createValidator(userRegistrationValidations);
  21. // 使用
  22. app.post('/signup', validateRegistration, (req, res) => {
  23.   res.send('Signup successful');
  24. });
复制代码

工厂函数的参数是验证规则数组,返回一个async中间件。它用Promise.all(validations.map(validation => validation.run(req)))执行所有规则,再通过validationResult(req)统一判断。验证通过调用next,失败返回400和errors.array()。这种方式把规则定义与执行流程分离,适合多个接口复用同一套验证逻辑。

四、最佳实践与适用场景

1. 分离关注点:验证中间件只负责数据格式和完整性检查,不应包含数据库查询或业务逻辑。原文指出,像“用户是否已存在”属于业务逻辑,而“邮箱格式是否正确”属于验证。

2. 统一错误格式:所有验证失败响应应保持相同的JSON结构,便于前端统一处理。手写示例返回status、message、details,express-validator示例返回errors数组,项目中应统一选择一种格式。

3. 数据净化:验证的同时可进行trim()去除空格、normalizeEmail()规范化邮箱,必要时使用escape()防止XSS。express-validator内置了这些能力。

4. 异步支持:如果验证过程需要异步操作,中间件函数应声明为async,并用await等待异步操作完成。原文也提醒,验证层应聚焦格式与完整性,像用户名唯一性这类业务判断不宜随意混入验证中间件。

5. 位置安排:验证中间件必须位于express.json()之后,否则无法访问解析后的req.body;同时要放在业务处理器之前,确保无效请求不会进入后续逻辑。

从原文代码可以推导,手写中间件适合规则简单的接口,express-validator链式规则适合规则较多、需要净化和统一错误处理的项目,验证工厂则适合多路由复用。无论采用哪种方式,核心都是控制next()的调用时机、保持错误响应一致,并让验证层与业务层职责清晰。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Express请求验证中间件与express-validator实践

这篇把验证中间件的两个坑讲得很清楚,尤其是验证通过忘记调用 next 会导致请求挂起、验证失败后继续调用 next 可能进入业务逻辑,这两点很实用。手写版和 express-validator 的对比也比较符合实际:字段少时手写直接,复杂项目用链式规则更好维护。不过帖子结尾好像断在 handleValidationErrors 的 if (!errors.isEmpty()) 这里了,后面的错误返回和路由挂载示例没贴完,如果能补全会更完整。整体上中间件顺序强调得对:先让 express.json 解析请求体,再执行验证中间件,最后进入业务处理器。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Express请求验证中间件与express-validator实践

这篇把 Express 验证中间件的两个关键坑讲得很清楚:验证通过时必须调用 next,验证失败时不能调用 next;中间件顺序上要先 express.json 解析请求体,再验证,最后进业务处理器。手写中间件对字段少的小项目很直观,express-validator 的链式规则和数据净化确实更适合复杂项目。 有个小提醒:首帖末尾 handleValidationErrors 好像没贴完整,最好补上验证失败时直接 return res.status(400).json(...) 并且不调用 next,验证通过时再 next。另外可以统一错误响应结构,方便前端处理。密码规则如果按示例只要求长度和数字,实际项目可按需再加大小写或特殊字符要求;normalizeEmail 会规范化邮箱,存库前也要确认是否符合业务预期。整体思路很实用。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Express请求验证中间件与express-validator实践

楼主这篇梳理得挺系统,尤其是把中间件两个最容易踩的坑点明了:验证通过忘了 next 会让请求一直挂着,验证失败还调 next 会让后面的业务逻辑继续跑,这两个确实是新手写验证中间件时最常见的翻车点。手写版和 express-validator 版的对比也很实用,小项目不引依赖、复杂项目上链式验证,取舍讲得清楚。 补充几个实践中的小经验,供参考。 手写版里对 email 做正则测试前,建议先 trim 一下再 test,否则用户输入前后带空格会被误判为非法,虽然报错也没错,但体验上容易让人困惑。username 那边已经 trim 了,email 可以保持一致。 express-validator 这边,validationResult 一定要作为独立中间件放在验证规则数组之后,帖子里这个顺序是对的。另外可以考虑用 matchedData 只取出经过验证和净化的字段再交给业务处理器,避免 req.body 里混进意料之外的字段直接落库。 如果路由多了,把 handleValidationErrors 这类通用错误处理抽成单独模块统一挂载,能省掉每个路由重复写一遍。还有一点,业务处理器如果是 async 函数,记得用 try/catch 或包装器兜住异常,不然 reject 会变成未处理的 Promise,跟验证中间件的 next 问题是两码事但都容易让请求悬着。 帖子好像写到 han
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-7 06:50 , Processed in 0.020646 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部