
1. 为什么需要动态生成接口签名与参数加密当你调用一个需要高安全级别的API时服务端通常会要求对请求进行签名验证。这就像你去银行办理业务需要出示身份证一样接口签名就是你的数字身份证用来证明这个请求确实是你发出的而不是别人伪造的。我遇到过这样一个真实案例某支付接口要求所有请求参数必须按照特定规则排序后加上密钥进行MD5加密生成签名。如果手动操作每次调用接口前都需要把所有参数按字母顺序排列拼接成key1value1key2value2的格式末尾加上密钥字符串对整个字符串做MD5加密把加密结果作为sign参数添加到请求中这个过程不仅繁琐而且容易出错。更麻烦的是如果参数有变动所有步骤都要重来一遍。这就是为什么我们需要在前置脚本中自动化完成这些操作。2. 前置脚本的工作原理与执行流程Postman的前置脚本就像是一个智能助手它会在你点击Send按钮后实际发送请求前自动执行。这个时机非常关键因为它允许我们在请求最终发出前对内容进行最后的加工和处理。具体执行顺序是这样的你填写好请求URL、参数和headers点击Send按钮Postman先执行Pre-request Script中的代码脚本执行完毕后Postman将处理后的参数打包发送最终的HTTP请求收到响应后执行Tests脚本如果有这种机制让我们可以在请求发出前动态修改任何内容包括URL参数、请求体、headers等。我在实际项目中经常用它来处理各种需要在请求前完成的操作比如签名生成、参数加密、token刷新等。3. 实现动态接口签名的完整方案3.1 准备签名所需的密钥和环境首先我们需要安全地管理签名密钥。千万不要把密钥直接写在脚本里我推荐使用Postman的环境变量来存储// 设置环境变量只需执行一次 pm.environment.set(api_secret, your_secret_key_here);在实际项目中我会为不同环境开发、测试、生产设置不同的密钥通过切换环境来自动使用对应的密钥。3.2 实现通用签名函数下面是一个我经过多次优化后的签名函数支持MD5、SHA1等常见算法/** * 生成接口签名 * param {Object} params 所有请求参数 * param {String} secret 密钥 * param {String} algorithm 加密算法默认MD5 */ function generateSignature(params, secret, algorithm md5) { // 1. 过滤掉sign参数本身和空值参数 const filteredParams {}; for (const key in params) { if (key ! sign params[key] ! undefined params[key] ! ) { filteredParams[key] params[key]; } } // 2. 按字段名排序 const sortedKeys Object.keys(filteredParams).sort(); // 3. 拼接键值对 let stringToSign ; sortedKeys.forEach(key { stringToSign ${key}${filteredParams[key]}; }); // 4. 末尾添加密钥 stringToSign key${secret}; // 5. 计算签名 const crypto require(crypto); const hash crypto.createHash(algorithm); hash.update(stringToSign); return hash.digest(hex).toUpperCase(); }3.3 在前置脚本中调用签名函数有了上面的工具函数在实际请求中使用就很简单了// 获取当前所有请求参数 const requestParams pm.request.url.query.toObject(); // 如果是POST请求还需要合并body中的参数 if (pm.request.body pm.request.body.mode raw) { try { const bodyParams JSON.parse(pm.request.body.raw); Object.assign(requestParams, bodyParams); } catch (e) { console.log(无法解析JSON body, e); } } // 获取环境变量中的密钥 const secret pm.environment.get(api_secret); // 生成签名 const sign generateSignature(requestParams, secret); // 将签名添加到请求参数中 pm.request.url.query.add({ key: sign, value: sign });4. 参数加密的进阶实现有些安全性要求更高的接口不仅需要签名还需要对参数值本身进行加密。下面介绍几种常见的加密方式在前置脚本中的实现方法。4.1 AES对称加密AES是最常用的对称加密算法适合对敏感数据进行加密function aesEncrypt(data, key, iv) { const crypto require(crypto); const cipher crypto.createCipheriv(aes-128-cbc, key, iv); let encrypted cipher.update(data, utf8, base64); encrypted cipher.final(base64); return encrypted; } // 使用示例 const encryptedData aesEncrypt( JSON.stringify({mobile: 13800138000}), 1234567890123456, // 16位密钥 1234567890123456 // 16位初始向量 ); pm.environment.set(encrypted_data, encryptedData);4.2 RSA非对称加密对于更高级别的安全需求可以使用RSA非对称加密function rsaEncrypt(data, publicKey) { const crypto require(crypto); const buffer Buffer.from(data, utf8); const encrypted crypto.publicEncrypt({ key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING }, buffer); return encrypted.toString(base64); } // 使用示例 const publicKey -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...省略 -----END PUBLIC KEY-----; const encrypted rsaEncrypt(敏感数据, publicKey); pm.request.body.raw JSON.stringify({ encrypted_data: encrypted });5. 实战中的常见问题与解决方案在实际使用过程中我遇到过不少坑这里分享几个典型问题的解决方法。5.1 时间戳同步问题很多接口要求请求必须携带时间戳且与服务器时间差不能超过一定范围。我们可以这样处理// 获取服务器时间假设有获取服务器时间的接口 pm.sendRequest({ url: pm.environment.get(base_url) /api/serverTime, method: GET }, (err, res) { if (!err) { const serverTime res.json().data.timestamp; const localTime Date.now(); const timeDiff Math.abs(localTime - serverTime); // 如果时间差超过5分钟使用服务器时间 const finalTimestamp timeDiff 300000 ? serverTime : localTime; pm.environment.set(current_timestamp, finalTimestamp); } });5.2 参数编码问题不同服务器对参数编码的处理可能不同我建议在前置脚本中统一处理function normalizeParams(params) { const normalized {}; for (const key in params) { if (params[key] ! undefined params[key] ! null) { // 统一转为字符串并编码 normalized[key] encodeURIComponent(params[key].toString()); } } return normalized; }5.3 调试技巧调试前置脚本时这些技巧很有帮助多用console.log()输出中间结果使用Postman的ConsoleView → Show Postman Console对于异步操作确保所有关键步骤都有日志可以使用pm.prompt()弹出重要变量的值// 调试示例 console.log(原始参数:, requestParams); console.log(排序后的参数字符串:, stringToSign); console.log(生成的签名:, sign); // 或者弹出重要信息 pm.prompt(签名生成结果, 签名: ${sign}\n用于签名的字符串: ${stringToSign});6. 高级应用场景除了基本的签名和加密前置脚本还能实现很多高级功能。6.1 自动刷新Token对于需要定期刷新的access token可以这样处理// 检查token是否即将过期 const tokenExpire pm.environment.get(token_expire); if (tokenExpire new Date(tokenExpire) new Date(Date.now() 300000)) { // 提前5分钟刷新token pm.sendRequest({ url: pm.environment.get(auth_url), method: POST, body: { mode: raw, raw: JSON.stringify({ refresh_token: pm.environment.get(refresh_token) }) } }, (err, res) { if (!err res.code 200) { const data res.json(); pm.environment.set(access_token, data.access_token); pm.environment.set(token_expire, new Date(Date.now() data.expires_in * 1000)); } }); }6.2 请求参数校验可以在发送请求前校验参数合法性// 校验手机号格式 function validateMobile(mobile) { return /^1[3-9]\d{9}$/.test(mobile); } const mobile pm.request.body.urlencoded.get(mobile); if (!validateMobile(mobile)) { pm.prompt(参数错误, 手机号格式不正确); // 阻止请求发送 pm.environment.set(should_abort, true); } // 在Tests脚本中检查是否应该中止 if (pm.environment.get(should_abort) true) { pm.environment.unset(should_abort); pm.test(请求被中止, () {}); throw new Error(请求因参数校验失败被中止); }6.3 批量请求处理对于需要批量发送的请求可以动态生成参数// 生成批量测试数据 const testCases []; for (let i 0; i 5; i) { testCases.push({ userId: test_${Math.random().toString(36).substr(2, 8)}, amount: Math.floor(Math.random() * 1000) 1 }); } // 存入环境变量供后续使用 pm.environment.set(test_cases, JSON.stringify(testCases));