☰
express-validator Sanitization Chain 完整指南:用法、API 与源码级原理解析
2026/10/10 2:14:06 网站建设 项目流程
  • 后端

【免费下载链接】express-validator

An express.js middleware for validator.js.

项目地址:https://gitcode.com/gh_mirrors/ex/express-validator
点击查看免费下载

导读

本文全面讲解 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); });

核心特性有两点:

  1. 链式叠加,顺序即语义:你可以在一条链上追加任意数量的净化器。中间件运行时,会按照它们被声明的顺序依次作用于目标字段,前一个净化器的输出会成为后一个净化器的输入。
  2. 就地修改:净化后的值会直接写回请求对象(如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! &lt;script&gt;runEvilFunction();&lt;/script&gt;'; });

执行机制: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.ts
  • check/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.

项目地址:https://gitcode.com/gh_mirrors/ex/express-validator
点击查看免费下载
上一篇:微信聊天记录导出完整指南:离线归档与年度报告
下一篇:E7Helper:第七史诗终极自动化助手完整指南 - 智能解放你的游戏时间

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询