Express 4项目中如何为特定路由配置独立的Body解析中间件
问题根因
Express 的中间件严格按照注册顺序匹配路径执行,HTTP 请求体本质是只可单次读取的 Stream 流,一旦被某个中间件消费完毕,后续所有中间件都无法再读取到原始流内容。
你当前的代码里,全局挂载的express.json()位于所有路由注册之前,请求到达 webhooks 路由时:
- 首先被全局 JSON 解析器拦截,完整读取请求流并解析为 JSON 对象挂在
req.body上 - 等请求走到 webhooks 路由上挂载的
express.raw()时,请求流已经被读空,根本拿不到原始 Buffer 内容,自然无法完成 HMAC 校验。
分步落地的解决方案
1. 调整全局 JSON 解析器的拦截范围
修改server.js中全局express.json()的配置,增加跳过逻辑,让它不对 Shopify Webhook 路径的请求做流解析,保留完整原始请求流给后续的 webhooks 路由使用:
// 替换原来的 app.use(express.json()) 行 app.use(express.json({ // 匹配到webhooks路径时直接跳过,不消费请求流 skip: (req) => req.originalUrl.startsWith('/shopify/app/webhooks') }))
2. 修正 webhooks 控制器的解析与校验逻辑
调整webhooks.controller.js的代码,把 raw 解析统一挂载到 webhooks 路由入口,同时修正 HMAC 计算的细节问题:
const express = require('express'); const router = express.Router(); const crypto = require('crypto'); const SHOPIFY_API_SECRET_KEY = process.env.SHOPIFY_API_SECRET_KEY; // 所有webhooks路由统一用raw格式解析,拿到原始Buffer router.use(express.raw({ type: 'application/json' })); router.post('/app/uninstalled', async (req, res) => { const hmac = req.get('X-Shopify-Hmac-Sha256'); // 直接用Buffer计算HMAC,不要手动做字符串转换/格式化 const hash = crypto .createHmac('sha256', SHOPIFY_API_SECRET_KEY) .update(req.body) .digest('base64'); if (hash === hmac) { console.log('Webhook来源校验通过'); return res.sendStatus(200); } console.log('Webhook来源校验失败,非Shopify发起的请求'); res.sendStatus(403); }); module.exports = router;
关键注意事项
- 计算 HMAC 时必须直接使用
express.raw()返回的 Buffer 对象,不要做toString()、JSON 序列化/反序列化、首尾 trim 等操作,任何格式改动都会导致计算出的哈希和 Shopify 端的值不匹配。 - 确保 skip 判断的路径和实际挂载 webhooks 路由的路径完全一致,不要误将其他需要 JSON 解析的业务接口跳过,导致普通 API 拿不到解析后的请求参数。
- Webhook 路由的链路里不要再挂载其他会读取请求流的中间件,避免再次出现流被提前消费的问题。
内容的提问来源于stack exchange,提问作者ahmetkca
相关产品推荐
相关产品推荐

