Node.js实现Mandrill webhook签名校验部分事件不匹配问题
Mandrill webhook 身份认证-签名校验
- 运行环境:Node.js
问题表现
现有签名校验逻辑仅在处理send、reject类型事件时可正常匹配签名,处理open、click等其他类型事件时,会出现authentication-signature not matched(签名不匹配)异常。
原有问题代码如下:
function generateSignature(webhook_key, url, params) { var signed_data = url; const param_keys = Object.keys(params); param_keys.sort(); param_keys.forEach(function (key) { signed_data += key + params[key]; }); hmac = crypto.createHmac('sha1', webhook_key); hmac.update(signed_data); return hmac.digest('base64'); } let url = "https://your-app-domain.com/default/MandrillXP-new"; let key = "abcd1234"; //your mandrill webhook api key let bodyPayload; if(event.isBase64Encoded){ bodyPayload = Buffer.from(event.body, 'base64').toString() }else{ bodyPayload = event.body } let splitData = req.body.split("=") let decodeData = decodeURIComponent(splitData[1]); var generatedSignature = generateSignature(key, url, { "mandrill_events": decodeData }) if (req.headers["x-mandrill-signature"]!== generatedSignature) { console.log("signature mismatch") }else{ console.log("signature matched") }
问题原因
- 手动用
split("=")切分请求体的逻辑完全不可靠:mandrill_events是JSON格式的事件数组,open、click类事件会携带完整的跳转链接,链接本身就包含大量=字符,切分后仅取索引为1的片段会直接截断参数值,导致参与签名计算的内容和Mandrill端不一致。 - 变量混用:代码中同时出现了
event.body和req.body两个请求体变量,在不同运行环境(如云函数、传统Node服务)下很容易取到错误的请求内容。 - 额外的解码逻辑破坏原始参数内容:手动调用
decodeURIComponent处理切分后的片段,会改变原始参数的字符串内容,进一步导致签名计算偏差。
修复后的正确实现
使用Node.js内置的querystring模块解析form格式的请求体,避免手动切分的问题,同时统一请求体读取逻辑,去掉多余的解码操作:
const crypto = require('crypto'); const qs = require('querystring'); function generateSignature(webhookKey, url, params) { let signedData = url; // 按参数key字典序排序 const sortedKeys = Object.keys(params).sort(); sortedKeys.forEach(key => { // querystring.parse会自动处理URL编码,直接拼接值即可 signedData += key + params[key]; }); const hmac = crypto.createHmac('sha1', webhookKey); hmac.update(signedData); return hmac.digest('base64'); } // 配置项 - 替换为实际业务配置 const WEBHOOK_URL = "https://your-app-domain.com/default/MandrillXP-new"; const WEBHOOK_KEY = "abcd1234"; // Mandrill后台对应webhook的API密钥 // 统一读取请求体,适配云函数Base64编码场景 let rawBody; if (event.isBase64Encoded) { rawBody = Buffer.from(event.body, 'base64').toString('utf-8'); } else { rawBody = event.body; // 传统Node服务可替换为req.rawBody,确保拿到原始form格式字符串 } // 用内置querystring模块解析form参数,自动处理=、&等特殊字符 const formParams = qs.parse(rawBody); // 计算签名 const generatedSignature = generateSignature(WEBHOOK_KEY, WEBHOOK_URL, formParams); // 签名比对 if (req.headers["x-mandrill-signature"] !== generatedSignature) { console.log("signature mismatch"); // 此处可添加拒绝请求的逻辑 } else { console.log("signature matched"); // 签名校验通过,处理后续业务逻辑 const events = JSON.parse(formParams.mandrill_events); }
注意:
WEBHOOK_URL必须和Mandrill后台配置的webhook地址完全一致,包括末尾是否带斜杠、是否携带查询参数,任何字符差异都会导致签名计算失败。
内容的提问来源于stack exchange,提问作者sundar_147
相关产品推荐
相关产品推荐

