将Axios+crypto请求签名迁移至Postman+CryptoJS遇签名不匹配问题
Postman请求签名不匹配(401错误)问题排查
问题描述
我尝试用Postman调用某API,API文档提供了基于Axios和Node.js crypto的请求签名示例。根据文档要求,X-App-Access-Sig签名需通过SHA256 HMAC算法,使用密钥拼接以下内容生成:
- 字符串格式的时间戳(即
X-App-Access-Ts头的值) - 大写HTTP方法
- 不含主机名的URI(包含查询参数)
- 请求体(无请求体则不包含)
我按照规则编写了Postman预请求脚本,但调用后返回401错误,提示请求签名不匹配。
API文档示例代码
const axios = require('axios'); const crypto = require('crypto'); var config = {}; config.baseURL= BASE_URL; axios.interceptors.request.use(createSignature, function (error) { return Promise.reject(error); }) function createSignature(config) { console.log('Creating a signature for the request...'); var ts = Math.floor(Date.now() / 1000); const signature = crypto.createHmac('sha256', SECRET_KEY); signature.update(ts + config.method.toUpperCase() + config.url); if (config.data instanceof FormData) { signature.update (config.data.getBuffer()); } else if (config.data) { signature.update (config.data); } config.headers['X-App-Access-Ts'] = ts; config.headers['X-App-Access-Sig'] = signature.digest('hex'); return config; }
我的Postman预请求脚本
var CryptoJs = require("crypto-js") console.log('Creating a signature for the request...'); const endpoint = pm.request.url.toString().split("{{BASE_URL}}").pop() var ts = Math.floor(Date.now() / 1000); const signature = CryptoJs.HmacSHA256(ts + pm.request.method.toUpperCase() + endpoint + pm.request.body, "{{SECRET_KEY}}") pm.request.headers.add({ key: 'X-App-Access-Ts', value: ts }) pm.request.headers.add({ key: 'X-App-Access-Sig', value: signature.toString(CryptoJs.enc.Hex) }) console.log(pm.request)
错误信息
{ "description": "Request signature mismatch", "code": 401, "correlationId": "req-53db2067-b0ec-4ce8-b333-387b0b20a955", "errorCode": 4003, "errorName": "app-token-signature mismatch" }
问题分析
对比API示例代码和Postman脚本,主要差异点如下:
- 请求体处理错误:Postman中直接使用
pm.request.body会获取到Postman的Body对象(而非原始请求体字符串/Buffer),导致签名拼接的内容和API要求不符。而Axios示例中会区分FormData和普通请求体,分别处理。 - URI获取不可靠:通过
split("{{BASE_URL}}")截取URI可能因BASE_URL变量的格式差异(如末尾是否带斜杠)导致截取结果错误。 - 时间戳类型隐式转换:虽然JS会自动将数字转为字符串,但显式转换更严谨,避免潜在的类型问题。
修复后的Postman预请求脚本
var CryptoJs = require("crypto-js") console.log('Creating a signature for the request...'); // 直接获取不含主机的URI(包含查询参数),避免split带来的格式问题 const endpoint = pm.request.url.getPathWithQuery(); // 显式将时间戳转为字符串,和Axios示例保持一致 var ts = Math.floor(Date.now() / 1000).toString(); // 初始化签名原始内容 let signatureRaw = ts + pm.request.method.toUpperCase() + endpoint; // 根据请求体模式处理请求体内容 if (pm.request.body) { switch(pm.request.body.mode) { case 'raw': // Raw模式直接拼接原始内容 signatureRaw += pm.request.body.raw; break; case 'formdata': // FormData转为URL编码格式(匹配Axios的FormData.getBuffer()对应的字符串逻辑) const formData = pm.request.body.formdata; let formStr = ''; formData.forEach((item, index) => { if (index > 0) formStr += '&'; formStr += `${encodeURIComponent(item.key)}=${encodeURIComponent(item.value)}`; }); signatureRaw += formStr; break; case 'urlencoded': // x-www-form-urlencoded拼接为查询字符串 const urlEncoded = pm.request.body.urlencoded; let urlStr = ''; urlEncoded.forEach((item, index) => { if (index > 0) urlStr += '&'; urlStr += `${encodeURIComponent(item.key)}=${encodeURIComponent(item.value)}`; }); signatureRaw += urlStr; break; // 文件上传等其他模式需根据API文档要求另行处理 } } // 计算HMAC SHA256签名 const signature = CryptoJs.HmacSHA256(signatureRaw, "{{SECRET_KEY}}"); // 添加请求头(先移除旧的避免重复) pm.request.headers.remove('X-App-Access-Ts'); pm.request.headers.remove('X-App-Access-Sig'); pm.request.headers.add({ key: 'X-App-Access-Ts', value: ts }); pm.request.headers.add({ key: 'X-App-Access-Sig', value: signature.toString(CryptoJs.enc.Hex) }); // 打印调试信息,方便对比排查 console.log('签名原始内容:', signatureRaw); console.log('生成的签名:', signature.toString(CryptoJs.enc.Hex));
修复说明
- URI获取优化:使用
pm.request.url.getPathWithQuery()直接获取符合要求的URI,避免依赖BASE_URL变量的格式。 - 请求体精准处理:针对Postman不同的请求体模式(raw、formdata、urlencoded)分别处理,确保拼接的请求体内容和Axios示例一致。
- 避免重复请求头:添加新的签名头前先移除旧的,防止多次执行预请求脚本导致头重复。
- 调试日志:添加签名原始内容和生成的签名日志,方便对比API示例的签名逻辑,快速定位问题。
内容的提问来源于stack exchange,提问作者sev
相关产品推荐
相关产品推荐

