如何使用Node.js和Express验证Shopify Webhook?
Node.js + Express 验证 Shopify Webhook:正确获取请求体的方法
目前Stack Overflow上的相关教程都已过时,2023年暂无针对Node.js和Express验证Shopify Webhook的官方更新文档。核心问题在于:必须获取与Shopify生成HMAC时完全一致的原始请求体,才能让本地哈希和Shopify返回的HMAC匹配。
关键原理
Shopify生成HMAC签名时,是基于原始未解析的请求体字节流计算的。如果使用express.json()或bodyParser.json()这类中间件,它们会解析JSON并重新格式化(比如调整空格、排序键),导致请求体内容和Shopify计算时的版本不一致,最终哈希对比失败。
正确实现步骤
- 使用文本类型的请求体解析中间件:用
bodyParser.text({ type: 'application/json' })(或Express 4.16+自带的express.text())获取原始的JSON字符串,避免解析过程中修改内容。 - 注意HMAC的编码格式:Shopify返回的
X-Shopify-Hmac-Sha256是base64编码的,不是hex,计算本地哈希时要对应使用digest('base64')。 - 安全获取共享密钥:不要硬编码Shopify共享密钥,建议从环境变量或安全配置中心获取。
修正后的完整代码
import express from 'express' import cors from 'cors' import * as crypto from 'crypto' import bodyParser from 'body-parser' const app = express() app.use(cors({ origin: true })) // 关键:用text中间件获取原始JSON请求体,不解析不修改 app.post('/order/create', bodyParser.text({ type: 'application/json' }), async (req, res) => { try { const hmacHeader = req.header('X-Shopify-Hmac-Sha256') const topic = req.header('X-Shopify-Topic') const shopDomain = req.header('X-Shopify-Shop-Domain') // 从安全渠道获取Shopify共享密钥,示例为异步方法 const sharedSecret = await shopifySharedSecret() if (!sharedSecret) { throw new Error('无法获取Shopify共享密钥') } // 用原始请求体生成HMAC,编码为base64(和Shopify一致) const generatedHmac = crypto .createHmac('sha256', sharedSecret) .update(req.body, 'utf8') // 指定编码确保字节流一致 .digest('base64') // 对比签名(建议用crypto.timingSafeEqual防时序攻击) const isValid = crypto.timingSafeEqual( Buffer.from(generatedHmac), Buffer.from(hmacHeader) ) if (!isValid) { throw new Error('HMAC验证失败') } // 验证通过,处理业务逻辑 console.log(`收到来自 ${shopDomain} 的 ${topic} Webhook,内容:`, req.body) res.status(200).send({ processed: true }) } catch (error) { console.error('Webhook处理失败:', error.message) res.status(403).send({ error: '验证失败' }) } }) // 示例:获取共享密钥的方法(替换为你的实际逻辑) async function shopifySharedSecret() { // 比如从环境变量、数据库或配置中心获取 return process.env.SHOPIFY_SHARED_SECRET } const PORT = process.env.PORT || 3000 app.listen(PORT, () => { console.log(`服务器运行在端口 ${PORT}`) })
额外注意事项
- 时序攻击防护:用
crypto.timingSafeEqual代替直接===对比,避免攻击者通过响应时间差异破解签名。 - 请求头校验:除了HMAC,建议同时校验
X-Shopify-Topic和X-Shopify-Shop-Domain,确保请求来源合法。 - 错误处理:验证失败时返回403状态码,Shopify会在重试几次后停止发送该Webhook。
内容的提问来源于stack exchange,提问作者Zelf
相关产品推荐
相关产品推荐

