- 后端
【免费下载链接】express-validator
An express.js middleware for validator.js.
导读
本文全面讲解 express-validator 6.11.0 的Sanitization Chain(净化链)API——它是挂载到 Express 路由上的中间件,用于在请求进入业务逻辑之前,按声明顺序就地(in-place)修改请求字段的值。通过本文,你将掌握如何组合使用 validator.js 的全部标准净化器(如trim、escape、toInt、normalizeEmail),以及 express-validator 独有的customSanitizer、default、replace、run、toArray、toLowerCase、toUpperCase等附加方法,并理解这些方法在 src/chain/sanitizers-impl.ts 等源码中的底层实现机制。
净化链的本质:一段修改请求数据的中间件
净化链(Sanitization Chain)本身就是一个Express 中间件,因此它应当被传递给路由处理器,与其他中间件(如body-parser)一样在请求处理管线中执行:
const { body } = require('express-validator'); app.get('/', body('trimMe').trim(), (req, res, next) => { // 如果 req.body.trimMe 原本是 " something ", // 经过净化后其值变为 "something" console.log(req.body.trimMe); });核心特性有两点:
- 链式叠加,顺序即语义:你可以在一条链上追加任意数量的净化器。中间件运行时,会按照它们被声明的顺序依次作用于目标字段,前一个净化器的输出会成为后一个净化器的输入。
- 就地修改:净化后的值会直接写回请求对象(如
req.body、req.query、req.params等)对应的字段位置,后续业务代码读取到的即是被清洗后的数据。
从源码结构看,净化链与校验链(Validation Chain)共享同一套构建体系:净化器方法通过 SanitizersImpl 实现,每个净化器都会被封装为一个Sanitization上下文项(见 src/context-items/sanitization.ts),追加到ContextBuilder内部维护的执行栈中。当中间件运行、ContextRunnerImpl.run()被调用时(见 src/chain/context-runner-impl.ts),这些上下文项会按入栈顺序依次执行,并在值发生变化时通过_.set()将新值写回请求对象的对应路径。
标准净化器:validator.js 全量函数开箱即用
validator.js 列出的所有净化器,在净化链内全部可用,express-validator 称之为"standard sanitizers"(标准净化器)。这意味着你可以直接调用诸如normalizeEmail、trim、toInt、blacklist、whitelist、escape、unescape、ltrim、rtrim、stripLow、toBoolean、toDate、toFloat等方法,参数与 validator.js 保持一致。
这一点由 src/chain/sanitizers.ts 的接口定义和 src/chain/sanitizers-impl.ts 的实现直接印证——每个标准净化器方法都会调用addStandardSanitization(),将对应的 validator.js 函数与选项参数一起封装进Sanitization上下文项:
// src/chain/sanitizers-impl.ts private addStandardSanitization(sanitizer: StandardSanitizer, ...options: any[]) { this.builder.addItem(new Sanitization(sanitizer, false, options)); return this.chain; } trim(chars?: string) { return this.addStandardSanitization(validator.trim, chars); } toInt(radix?: number) { return this.addStandardSanitization(validator.toInt, radix); } normalizeEmail(options?: Options.NormalizeEmailOptions) { return this.addStandardSanitization(validator.normalizeEmail, options); }常见标准净化器及参数速查:
| 方法 | 参数 | 说明 |
|---|---|---|
trim(chars?) | chars:可选,要去除的字符集合 | 去除字符串两端空白(或指定字符) |
ltrim(chars?)/rtrim(chars?) | chars:可选 | 仅去除左侧 / 右侧空白或指定字符 |
escape()/unescape() | 无 | HTML 实体转义 / 反转义,常用于防 XSS |
blacklist(chars)/whitelist(chars) | chars:必填字符集合 | 移除黑名单字符 / 仅保留白名单字符 |
stripLow(keep_new_lines?) | keep_new_lines:可选,默认false,为true时保留换行符 | 移除 ASCII 控制字符 |
normalizeEmail(options?) | 见 validator.js 文档 | 规范化邮箱字符串 |
toInt(radix?) | radix:可选进制,默认 10 | 转换为整数 |
toFloat() | 无 | 转换为浮点数 |
toBoolean(strict?) | strict:可选,true时仅'1'/'true'为真 | 转换为布尔值 |
toDate() | 无 | 转换为Date对象 |
关于完整标准净化器清单与选项:validator.js 官方文档(其仓库的 Sanitizers 章节)列出了全部可用净化器及详细选项,建议在需要完整参数表时查阅。
重要限制——输入必须为字符串:由于 validator.js 只接受
string作为输入,任何需要被标准净化器处理的值(包括数组和对象)都会先被转换为字符串再交给净化器。转换逻辑位于 src/context-items/sanitization.ts:标准净化器执行时,若当前值不是数组则包装为数组,对每个元素调用toString()(见 src/utils.ts,Date会被转为 ISO 字符串,null/undefined/NaN转为空串),最后再写回请求对象。这正是"数组/对象无法被标准净化器正确净化"这一常见困惑的根源,也是你在使用时应优先选择customSanitizer处理复杂结构的原因。
附加方法概览
除标准净化器外,净化链还提供以下 express-validator 专属方法(接口见 src/chain/sanitizers.ts,实现见 src/chain/sanitizers-impl.ts):
| 方法 | 作用 | 返回值 |
|---|---|---|
.customSanitizer(sanitizer) | 追加自定义净化函数,可同步或异步返回新值 | 当前净化链实例 |
.default(default_value) | 当前值属于['', null, undefined, NaN]时替换为默认值 | 当前净化链实例 |
.replace(values_to_replace, new_value) | 当前值在给定列表中时替换为新值 | 当前净化链实例 |
.run(req) | 以命令式方式执行净化链 | Promise |
.toArray() | 将值转换为数组,undefined转为空数组 | 当前净化链实例 |
.toLowerCase()/.toUpperCase() | 转为小写 / 大写,非字符串原样返回 | 当前净化链实例 |
附加方法详解与实战示例
.customSanitizer(sanitizer)—— 自定义净化逻辑
- 签名:
sanitizer(value, { req, location, path }) sanitizer接收被净化字段的值,以及包含 Express 请求对象req、字段所在位置location、字段路径path的元数据对象;- 它必须同步返回新值(若返回 Promise,实现中也会通过
Promise.resolve吸收,见 src/context-items/sanitization.ts); - 返回:当前净化链实例(便于继续链式调用)。
典型场景:根据请求上下文决定字段的类型转换。例如 URL 参数:id可能代表用户 ID 或数字 ID,需结合查询参数判断:
const { param } = require('express-validator'); app.get( '/object/:id', param('id').customSanitizer((value, { req }) => { return req.query.type === 'user' ? ObjectId(value) : Number(value); }), objectHandler, );在 src/chain/sanitizers-impl.ts 中,customSanitizer会把传入函数以custom: true标记封装进Sanitization项;运行时会跳过字符串转换,直接将原始值(包括数组、对象)交给自定义函数处理。
.default(default_value)—— 空值兜底
- 当前值包含在
['', null, undefined, NaN]中时,将其替换为默认值; - 返回:当前净化链实例。
app.post('/', body('username').default('foo'), (req, res, next) => { // 'bar' => 'bar' // '' => 'foo' // undefined => 'foo' // null => 'foo' // NaN => 'foo' });源码实现将default直接委托为customSanitizer(见 src/chain/sanitizers-impl.ts),且替换时使用_.cloneDeep(default_value)深拷贝默认值。这意味着如果默认值是对象,每次请求写入的都是独立副本,不会因引用共享而产生状态污染——这一点由 sanitizers-impl.spec.ts 中"两次运行返回的默认对象互不相等(not.toBe)"的测试用例直接验证。
.replace(values_to_replace, new_value)—— 指定值替换
- 当前值包含在给定的数组中时,替换为新值;
- 返回:当前净化链实例。
app.post('/', body('username').replace(['bar', 'BAR'], 'foo'), (req, res, next) => { // 'bar_' => 'bar_' // 'bar' => 'foo' // 'BAR' => 'foo' console.log(req.body.username); });实现细节(见 src/chain/sanitizers-impl.ts):
- 若
values_to_replace不是数组,会被自动包装成单元素数组,因此.replace('bar', 'foo')与.replace(['bar'], 'foo')等价; - 替换值同样经过
_.cloneDeep深拷贝; - 与
default不同,replace不处理空值——''、null、undefined、NaN只要不在替换列表中就会原样保留(测试见 sanitizers-impl.spec.ts)。
.run(req)—— 命令式执行净化链
- 返回:一个 Promise,在净化链执行完毕后 resolve;
- 适用于不想把净化链当作中间件挂载,而希望在路由处理函数内部手动控制执行时机的场景。
const { check } = require('express-validator'); app.post('/create-post', async (req, res, next) => { // BEFORE: // req.body.content = ' hey your forum is amazing! <script>runEvilFunction();</script> '; await check('content').escape().trim().run(req); // AFTER: // req.body.content = 'hey your forum is amazing! <script>runEvilFunction();</script>'; });执行机制:run(req)最终走到 ContextRunnerImpl.run(),它会按字段选择结果逐个执行上下文栈中的净化项,并仅在值真正变化时把新值写回req(通过_.set避免为原本不存在的键写入undefined)。check('content')默认同时作用于req.body、req.cookies、req.headers、req.params、req.query五个位置(见 src/middlewares/validation-chain-builders.ts),上例中实际命中的是req.body.content。
.toArray()—— 强制转为数组
- 将当前值转换为数组:已是数组则保持原样,单个值包装为数组,
undefined转为空数组; - 返回:当前净化链实例。
app.post('/', [body('checkboxes').toArray()], (req, res, next) => { // ['foo', 'bar'] => ['foo', 'bar'] // 'foo' => ['foo'] // undefined => [] console.log(req.body.checkboxes); });实现位于 src/chain/sanitizers-impl.ts,注意它是用customSanitizer实现的(不经过字符串转换),因此''会变成['']、null会变成[null],这与undefined得到[]的行为不同(测试见 sanitizers-impl.spec.ts)。这在处理复选框、多选等可能缺失的字段时非常实用。
.toLowerCase()/.toUpperCase()—— 大小写转换
- 将字符串值转为小写 / 大写;非字符串值(含
null、undefined)原样返回; - 返回:当前净化链实例。
app.post('/', [body('username').toLowerCase()], (req, res, next) => { // 'Foo' => 'foo' // undefined => undefined // null => null console.log(req.body.username); }); app.post('/', [body('username').toUpperCase()], (req, res, next) => { // 'Foo' => 'FOO' // undefined => undefined // null => null console.log(req.body.username); });同样以customSanitizer实现,仅当typeof value === 'string'时才执行转换(见 src/chain/sanitizers-impl.ts),因此不会像标准净化器那样把非字符串强制转成字符串,安全地保留了原始类型(测试见 sanitizers-impl.spec.ts)。
常见组合模式与最佳实践
模式一:先净化、后校验
净化链与校验链可以级联在同一字段上(同一中间件返回值既含净化方法又含校验方法),推荐的顺序是先做类型/格式归一化,再做业务校验,例如统一大小写后检查邮箱格式:
const { body } = require('express-validator'); app.post( '/register', body('email').trim().normalizeEmail().isEmail().withMessage('邮箱格式不正确'), body('username').trim().toLowerCase().isLength({ min: 3, max: 20 }), (req, res) => { // 此时 req.body.email 与 req.body.username 均已净化 }, );模式二:防 XSS 输入清洗
escape()会把 HTML 特殊字符转为实体,trim()去除首尾空白,两者组合可显著降低存储型 XSS 风险(如开篇.run(req)示例所示)。注意escape()属于标准净化器,会先将输入转为字符串。
模式三:表单默认值与枚举归一化
用default()处理缺失字段、用replace()将多种写法(如'bar'/'BAR')统一为规范值,减少下游分支判断。
使用建议与注意事项
- 就地修改的副作用:净化链会直接改写
req对象上的字段值,因此应放在业务逻辑之前执行;同时注意同一字段被多条中间件同时声明时,执行顺序取决于中间件注册顺序。 - 复杂结构请用
customSanitizer:数组、嵌套对象等非字符串结构应交给自定义净化器处理,避免标准净化器的字符串强制转换造成数据形状改变。 default/replace的克隆语义:传入对象/数组作为默认值或替换值时,每请求得到独立副本,无需担心跨请求引用共享。- 命令式执行记得
await:.run(req)返回 Promise,需await后再读取净化结果。
源码路径速查
- 净化链附加方法接口定义:src/chain/sanitizers.ts
- 净化器具体实现(含
default/replace/toArray/toLowerCase/toUpperCase/标准净化器委托):src/chain/sanitizers-impl.ts - 单个净化项的运行时行为(字符串转换、数组包装、值回写):src/context-items/sanitization.ts
- 净化链/校验链的执行引擎(字段选择、顺序执行、写回请求):src/chain/context-runner-impl.ts
toString转换工具:src/utils.tscheck/body/param/query等链构建器:src/middlewares/validation-chain-builders.ts- 相关单元测试(验证各净化器的行为与克隆语义):src/chain/sanitizers-impl.spec.ts
结语
Sanitization Chain 是 express-validator 中"数据清洗"能力的统一入口:标准净化器提供 validator.js 的全部字符串处理函数,附加方法则补齐了自定义逻辑、默认值、替换、命令式执行与类型转换等场景。理解其"顺序执行 + 就地写回 + 标准净化器先转字符串"的三大底层语义,就能在实际项目中写出既安全又可靠的输入预处理管线。
- 后端
【免费下载链接】express-validator
An express.js middleware for validator.js.
相关推荐
华硕笔记本性能调优神器:开源控制工具G-Helper深度解析
华硕笔记本性能调优神器:开源控制工具G Helper深度解析 对于追求极致性能的华硕笔记本用户来说,G Helper这款开源硬件控制工具无疑是 华硕笔记本性能优
桌面应用系统编程深入理解express-validator中的Sanitization Chain API
深入理解express validator中的Sanitization Chain API 什么是Sanitization Chain 在express val
后端JSL-joysafety-v1未来展望:从文本审核到多模态安全审核的技术演进
JSL joysafety v1未来展望:从文本审核到多模态安全审核的技术演进 在AI内容安全领域,JSL joysafety v1已经成为了文本安全审核的重要
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考