实战指南:从 trim、escape 到自定义 Sanitizer)
后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载导读在 HTTP 请求处理中数据校验负责确认格式是否正确而数据清洗Sanitization则负责让数据去除噪声——例如去掉首尾空格、转义 HTML 特殊字符、规范化邮箱、把字符串转换为数字或布尔值。本文以 express-validator 5.3.0 版本的官方 Sanitization 文档为主线结合仓库源码完整讲解如何在同一条链上组合校验与清洗、如何用express-validator/filter下的sanitize/sanitizeBody等入口处理未参与校验的字段、清洗链的执行顺序与请求可变性以及如何编写自定义 Sanitizer。读完本文你将能安全、规范地处理来自 body、query、params、cookies 等位置的原始输入。为什么需要 Sanitization接收 HTTP 请求数据不仅要保证数据格式正确还要保证它没有噪声。例如用户在评论区输入了 Hello world :) 如果不做任何处理这段内容会被原样存储并在页面上直接渲染带来 XSS 风险与存储脏数据问题。validator.js 提供了一批开箱即用的 Sanitizerexpress-validator 将这些方法全部透传到清洗链上让开发者可以在校验的同时顺手完成数据清洗从而减少校验过、但没清理过的脏数据进入业务逻辑。在 5.3.0 版本中express-validator 的 API 按职责分为两个入口这是 v5 的典型用法express-validator/check提供check、body、query、params、cookie、header等校验链构建器express-validator/filter提供sanitize、sanitizeBody、sanitizeCookie、sanitizeParam、sanitizeQuery、buildSanitizeFunction等清洗入口以及matchedData。在校验链上直接串联清洗方法清洗最常见、最省事的做法是对于已经被校验的字段直接在同一个链上追加清洗方法。因为校验链本身就是中间件清洗方法只是追加到同一上下文中的一个个清洗步骤。来看官方文档的核心示例feature-sanitization.mdconst express require(express); const { body } require(express-validator/check); const { sanitizeBody } require(express-validator/filter); const app express(); app.use(express.json()); app.post(/comment, [ body(email) .isEmail() .normalizeEmail(), body(text) .not().isEmpty() .trim() .escape(), sanitizeBody(notifyOnReply).toBoolean() ], (req, res) { // Handle the request somehow });这段代码说明了两种典型场景已校验字段email和text参与了校验因此在同一条链上直接追加normalizeEmail()、trim()、escape()即可完成清洗无需额外中间件未校验字段notifyOnReply不需要校验但需要把表单提交的字符串如true/on/1转换成 JavaScript 布尔值此时使用 filter API 中的sanitizeBody单独建立清洗链。重要Sanitization 会就地修改请求官方文档特别强调了一条关键语义原文以 Important 提示sanitization mutates the request.也就是说清洗不是返回一个新值而是直接改写请求对象中的字段。例如req.body.text原本是 Hello world :) 经过trim().escape()之后它的值就变成了Hello world :gt;)。后续中间件与路由处理器读到的都是清洗后的值。这一点与 api-sanitization-chain.md 中的说明一致清洗链是中间件运行时就地修改每个字段按指定顺序依次应用每个 Sanitizer。filter API处理未参与校验的字段当字段不参与校验、或希望独立管理清洗逻辑时使用 filter APIrequire(express-validator/filter)提供的清洗入口。sanitize(fields) 与位置限定变体sanitize(fields)fields为字段名字符串或字符串数组返回一个 Sanitization Chain。它会在以下请求对象中查找并清洗字段req.bodyreq.cookiesreq.paramsreq.query注意req.headers目前不被支持。如果同一字段出现在多个位置例如req.body.id和req.query.id同时存在则该字段的所有实例都会被清洗。sanitizeBody(fields)等同于sanitize(fields)但只清洗req.bodysanitizeCookie(fields)只清洗req.cookiessanitizeParam(fields)只清洗req.paramssanitizeQuery(fields)只清洗req.query。buildSanitizeFunction(locations)自定义位置组合如果需要在多个位置执行同一清洗逻辑可以用buildSanitizeFunction(locations)生成定制版sanitize()其中locations是body、cookies、params、query的任意组合const { buildSanitizeFunction } require(express-validator/filter); const sanitizeBodyAndQuery buildSanitizeFunction([body, query]); app.put(/update-product, [ // id 无论来自 req.body 还是 req.query都会被转换为 int sanitizeBodyAndQuery(id).toInt() ], productUpdateHandler);matchedData(req[, options])只取清洗/校验后的数据filter API 还提供了matchedData(req[, options])它从请求中提取通过 check API 校验的数据并组装成对象支持嵌套路径与通配符。可选参数includeOptionals设为true时包含可选字段数据默认falseonlyValidData设为false时包含未通过校验字段的数据默认truelocations指定提取位置body、cookies、headers、params、query默认undefined表示所有位置。// 假设请求为 // req.query { from: 2017-01-12 } // req.body { to: 2017-31-12 } app.post(/room-availability, check([from, to]).isISO8601(), (req, res, next) { const queryData matchedData(req, { locations: [query] }); const bodyData matchedData(req, { locations: [body] }); const allData matchedData(req); console.log(queryData); // { from: 2017-01-12 } console.log(bodyData); // { to: 2017-31-12 } console.log(allData); // { from: 2017-01-12, to: 2017-31-12 } });清洗链顺序即结果清洗链与校验链一样是中间件必须传给 Express 路由处理函数。Sanitizer 按声明顺序依次执行前一个的输出是后一个的输入app.get(/, sanitizeBody(trimMe).trim(), (req, res, next) { // 如果 req.body.trimMe 原本是 something // 清洗后的值将是 something console.log(req.body.trimMe); });从源码看清洗链的执行机制从源码结构看清洗步骤最终会被包装成 Sanitization 上下文项挂在上下文构建器ContextBuilder上随中间件运行export class Sanitization implements ContextItem { constructor( private readonly sanitizer: StandardSanitizer | CustomSanitizer, private readonly custom: boolean, private readonly options: any[] [], ... ) {} async run(context: Context, value: any, meta: Meta) { ... if (this.custom) { const newValue await runCustomSanitizer(); context.setData(path, newValue, location); return; } const values Array.isArray(value) ? value : [value]; const newValues values.map(value { return (this.sanitizer as StandardSanitizer)(this.stringify(value), ...this.options); }); context.setData(path, values ! value ? newValues[0] : newValues, location); } }由 sanitization.spec.ts 中的测试可以印证几个关键行为清洗结果会写回上下文并最终写回请求测试断言context.setData以清洗后的新值被调用见persists sanitized value back into the context数组字段逐个清洗标准 Sanitizer 对数组中的每一项分别调用测试calls it for each item in array field验证了[1, 42]会各调用一次标准 Sanitizer 收到的是字符串化后的值在调用标准 Sanitizer 前字段值会先经过 utils.ts 的 toString 处理——Date 转为 ISO 字符串对象调用其toString()null/undefined/NaN 转为空字符串自定义 Sanitizer 收到(value, meta)meta中包含{ req, location, path }等上下文信息且支持返回 Promise源码中通过Promise.resolve包装标准 Sanitizer 可接收附加参数这些参数作为options原样透传测试calls it with the options验证了这一点。清洗链上的完整 Sanitizer 清单从 Sanitizers 接口定义 与 SanitizersImpl 实现 看5.3.0 的清洗链除了透传 validator.js 的标准清洗方法外还额外提供了两类便捷方法标准清洗来自 validator.js按声明顺序执行方法作用可选参数blacklist(chars)删除字符串中出现在黑名单字符集里的字符chars必填escape()HTML 转义、、、、等—unescape()HTML 反转义—ltrim(chars?)去除左侧空白或指定字符集charsrtrim(chars?)去除右侧空白或指定字符集charstrim(chars?)去除两侧空白或指定字符集charsnormalizeEmail(options?)规范化邮箱小写、去除点号别名等NormalizeEmailOptionsstripLow(keep_new_lines?)去除 ASCII 控制字符keep_new_linestoBoolean(strict?)转换为布尔值stricttoDate()尝试转换为 Date—toFloat()转换为浮点数—toInt(radix?)转换为整数radixtoLowerCase()/toUpperCase()大小写转换仅对字符串生效非字符串原样返回见实现—whitelist(chars)仅保留白名单字符chars必填便捷/自定义清洗express-validator 自身实现default(default_value)当字段值为、null、undefined或NaN时替换为default_value通过_.cloneDeep深拷贝避免引用共享replace(values_to_replace, new_value)把指定值可传数组替换为new_valuetoArray()把非数组值包装为单元素数组undefined变为空数组customSanitizer(sanitizer)见下文。customSanitizer自定义清洗逻辑当 validator.js 自带的清洗方法不够用时使用.customSanitizer(sanitizer)编写自定义清洗app.get(/object/:id, sanitizeParam(id).customSanitizer((value, { req }) { return req.query.type user ? ObjectId(value) : Number(value); }), objectHandler);签名说明api-sanitization-chain.mdsanitizer(value, { req, location, path })接收被清洗字段的值以及包含 Express 请求对象、位置body/query/params/cookies和字段路径的 meta必须同步返回新值源码实现中虽以Promise.resolve包装返回值但文档明确指出当前要求同步函数。更多自定义清洗示例如转换为 MongoDB ObjectID可参考 feature-custom-validators-sanitizers.md。通配符Wildcards批量清洗数组与对象字段如果要对一个数组的所有元素或某个对象的全部键应用同一清洗规则可以使用*通配符详见 feature-wildcards.md。例如校验所有地址的邮政编码合法并把每个地址的number字段清洗为整数const express require(express); const { check } require(express-validator/check); const { sanitize } require(express-validator/filter); const app express(); app.use(express.json()); app.post(/addresses, [ check(addresses.*.postalCode).isPostalCode(), sanitize(addresses.*.number).toInt() ], (req, res) { // Handle the request });它既能处理数组形式的地址列表{ addresses: [ { postalCode: 2010, number: 500 }, { postalCode: , number: 501 } ] }也能处理键名预定义的对象形式{ addresses: { home: { postalCode: , number: 501 }, work: { postalCode: 2010, number: 500 } } }配合前述 Sanitization 实现 中对数组逐项清洗的行为sanitize(addresses.*.number).toInt()会把每个地址的number字符串转换为整数而校验失败如postalCode为空字符串的地址也不会被清洗跳过——校验与清洗在各自链上独立处理。完整实践建议已校验字段把清洗方法直接串在check/body/query等校验链上减少中间件数量并保证只有通过校验的数据才会被清洗未校验字段使用 filter API 的sanitize/sanitizeBody/sanitizeQuery/sanitizeParam/sanitizeCookie单独建链或使用buildSanitizeFunction组合多个位置牢记顺序trim().escape()与escape().trim()结果可能不同Sanitizer 严格按声明顺序执行牢记就地修改清洗会改写req后续中间件读到的都是清洗后的值如需原始值应在清洗前自行保存自定义需求优先组合标准 Sanitizer不够用时用customSanitizer同步、default或replace补齐。小结数据清洗与数据校验是同一枚硬币的两面校验保证正确清洗保证干净。express-validator 5.3.0 通过express-validator/filter的清洗入口与校验链上的内联清洗方法将 validator.js 的 Sanitizer 能力完整带入 Express 中间件体系并在源码层面通过 Sanitization 上下文项 实现了逐字段、逐项、按序、就地的清洗语义。掌握本文介绍的链式组合、位置限定、通配符与自定义 Sanitizer 四类用法即可在真实项目中构建可靠的输入净化管线。赞分享后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载相关推荐express-validator v6 数据清洗Sanitization完整指南从内置 sanitizer 到自定义清洗链express validator v6 数据清洗Sanitization完整指南从内置 sanitizer 到自定义清洗链 导读 HTTP 请求输入除后端PDF补丁丁 PDF编辑教程书签、合并拆分、批量处理四大场景实操PDF补丁丁 PDF编辑教程书签、合并拆分、批量处理四大场景实操 周五下午你拿到十几个待整理的PDF有的没有书签有的页面尺寸不一有的要合并成一个文件。后端express-validator 请求数据清洗Sanitization实战从净化输入到链式清洗express validator 请求数据清洗Sanitization实战从净化输入到链式清洗 HTTP 请求携带的数据往往既需要校验格式也需要剔除噪后端上一篇Pinpoint监控指标告警聚合规则动态阈值调整终极指南下一篇从源码到APKandroid-ffmpeg静态库在Android应用中的集成实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考