Postman集成ForgeJS实现RSA加解密:API测试安全接口实战指南

发布时间:2026/7/29 15:55:16
Postman集成ForgeJS实现RSA加解密:API测试安全接口实战指南 1. 项目概述为什么API测试需要RSA加解密如果你经常做API测试尤其是涉及支付、用户认证、敏感数据传输的场景一定遇到过这样的需求接口要求对请求参数进行RSA加密或者需要对响应结果进行RSA解密来验证。直接在代码里写加解密逻辑当然可以但调试起来非常麻烦每次改点东西都要重新运行整个项目。Postman作为我们最常用的API调试工具如果能直接在它的Pre-request Script里搞定加解密那测试效率会提升好几个量级。这就是我们今天要解决的问题。单纯用Postman内置的CryptoJS库它主要支持AES、DES这些对称加密对RSA这种非对称加密的支持很弱。而forgeJS是一个纯JavaScript实现的强大密码学工具库功能齐全正好能补上这个短板。本教程的目的就是手把手教你在Postman里集成forgeJS实现一套完整的RSA加解密流程让你在测试需要加密验签的接口时能像测试普通接口一样顺畅。整个过程的核心思路是通过Postman的脚本功能动态加载forgeJS这个第三方库然后利用它生成密钥、进行加密和解密操作。这不仅仅是写几行代码更涉及到Postman脚本的执行环境、库的加载方式、密钥的管理以及如何与接口参数动态结合等一系列实操细节。接下来我会从设计思路开始一步步拆解并附上我踩过的所有坑和对应的解决方案。2. 核心思路与工具选型解析2.1 为什么选择ForgeJS而非其他库首先我们得明白在Postman这个特殊环境里选型的限制。Postman的脚本运行在Node.js环境如果你用桌面版或一个受限的浏览器沙盒环境如果你用Web版。它不支持直接npm install所以我们需要的库必须能通过CDN直接加载或者以单个JS文件的形式引入。市面上常见的JavaScript RSA库有node-rsa、jsencrypt、crypto-jsRSA支持有限和forge。node-rsa严重依赖Node.js原生模块在Postman里基本没法用。jsencrypt是个轻量级的选择它专注于RSA加解密但功能相对单一对于需要处理PEM格式密钥、或者进行更复杂操作如签名验签的场景支持不够友好。forge通常被称为forgeJS则是一个功能全面的密码学工具包。它纯JavaScript实现不依赖任何原生模块这意味着它可以在浏览器和Node.js中无缝运行完美契合Postman的环境。它支持RSA、AES、DES、SHA等多种算法能轻松地生成密钥对、解析各种格式PEM、ASN.1的密钥、进行加密解密和签名验签。虽然体积比jsencrypt大一点但对于Postman脚本来说这点体积差异可以忽略不计换来的却是极大的灵活性和功能完整性。注意Postman的Web版本对脚本的限制比桌面版更多特别是加载外部资源。为确保最佳兼容性和稳定性强烈建议在整个教程中使用Postman的桌面应用程序。本文所有操作均基于桌面版。2.2 整体方案设计我们的目标是在Postman中对一个API请求实现“参数自动加密响应自动解密”的流程。这需要分两步走Pre-request Script请求前脚本在这里我们需要加载forgeJS库然后从某个地方比如环境变量获取公钥对请求体如JSON中的某个字段进行RSA加密并用加密后的结果替换原始值。Tests Script测试脚本在收到响应后同样加载forgeJS从环境变量获取私钥对响应体中加密的数据进行解密并将解密结果存入变量用于后续的断言验证。这里有一个关键点密钥的管理。我们绝对不能把私钥硬编码在脚本里。Postman提供了环境变量Environments和全局变量Globals来安全地存储这些敏感信息。通常我会将“公钥”和“私钥”的PEM字符串分别存入环境变量例如rsa_public_key和rsa_private_key。这样切换测试环境如测试、预发布时只需切换对应的环境密钥也会自动切换。整个方案的架构如下图所示概念描述用户发起请求 - Pre-request Script介入加载ForgeJS并用公钥加密特定参数 - 发送加密后的请求 - 服务器处理并返回加密响应 - Tests Script介入加载ForgeJS并用私钥解密响应 - 验证解密后的数据。3. 环境准备与ForgeJS加载3.1 获取并引入ForgeJS库ForgeJS没有提供一个直接的、适用于Postman的CDN单文件。官方推荐通过npm安装但我们可以从它的GitHub发布页或一些公共CDN找到浏览器版本的打包文件。最可靠的方法是使用一个稳定的CDN链接。经过测试jsdelivrCDN上的Forge版本兼容性很好。我们将在Postman脚本中通过动态创建script标签的方式来加载它。这里有一个非常重要的技巧Postman的脚本环境是沙盒化的但它支持基本的浏览器DOM操作仅限于脚本内部我们可以利用pm.sendRequest的变通方法或者更直接地使用eval来执行远程JS代码需谨慎但最优雅的方式是使用Postman内置的require函数吗不Postman的脚本环境并不完全等同于Node其require功能有限。实际上最通用且稳定的方法是使用pm.sendRequest配合eval。我们先通过一个请求获取到forge.js的源代码然后将其eval到当前脚本上下文中。这听起来有点“黑魔法”但却是Postman社区公认的加载第三方库的有效手段。具体操作如下我们将在Pre-request Script中写入// 检查forge是否已加载避免重复加载 if (!pm.globals.has(forge)) { // 使用jsdelivr CDN加载forge const forgeUrl https://cdn.jsdelivr.net/npm/node-forge1.3.1/dist/forge.min.js; pm.sendRequest(forgeUrl, function (err, response) { if (err) { console.error(加载forgeJS失败:, err); return; } // 关键步骤将获取到的库代码eval执行使其注入全局作用域 eval(response.text()); // 将forge对象设置为全局变量方便后续使用 pm.globals.set(forge, forge); console.log(forgeJS加载成功); }); } else { console.log(forgeJS已加载直接使用。); forge pm.globals.get(forge); }这段代码的逻辑是首先检查全局变量中是否已缓存了forge对象。如果没有就发送一个请求到CDN获取压缩后的forge.min.js文件。获取成功后用eval(response.text())执行这段代码这会将forge这个全局对象注入到当前脚本环境。然后我们再将这个forge对象存入Postman的全局变量这样在同一个集合的其他请求中就可以直接读取无需重复加载节省时间和避免潜在错误。实操心得pm.sendRequest是异步的。这意味着如果你的加密逻辑直接写在这段代码的后面可能会在forge库还没加载完成时就执行导致报错“forge is not defined”。因此更稳健的做法是将所有依赖forge的加密操作都放在pm.sendRequest的回调函数内部。或者采用一种“同步化”的技巧将核心加密函数定义为回调但这会让代码结构变得复杂。对于新手我建议先采用“先加载后使用”的模式在首次运行请求时可能会因为异步问题失败一次但第二次运行就会成功因为forge已被缓存。在“常见问题”章节我会给出一个更优雅的同步处理方案。3.2 密钥的存储与格式处理接下来我们需要准备RSA密钥对。你可以用OpenSSL命令生成也可以用forgeJS自己生成。为了方便这里给出用OpenSSL生成一对PKCS#8格式PEM密钥的命令# 生成2048位的RSA私钥 openssl genrsa -out private_key.pem 2048 # 从私钥中提取出公钥 openssl rsa -in private_key.pem -pubout -out public_key.pem生成的private_key.pem和public_key.pem就是标准的PEM格式文件。你需要将这两个文件的内容包括-----BEGIN XXX-----和-----END XXX-----这些头尾行完整地复制出来。在Postman中点击眼睛图标打开环境变量管理或全局变量。新建两个变量例如rsa_public_key: 值为public_key.pem的全部内容。rsa_private_key: 值为private_key.pem的全部内容。格式处理是关键。PEM密钥是包含换行符的文本。当你复制到Postman的变量值时换行符会被保留。但是在脚本中读取环境变量时有时换行符可能会引发问题。forgeJS的pki.publicKeyFromPem和pki.privateKeyFromPem方法能够很好地处理带换行符的标准PEM字符串。所以通常你不需要做额外处理。但如果你的密钥是连续字符串没有换行也可以直接使用。在脚本中我们这样获取密钥const publicKeyPem pm.environment.get(rsa_public_key); const privateKeyPem pm.environment.get(rsa_private_key);4. 核心加解密功能实现详解4.1 RSA公钥加密实战假设我们有一个登录接口密码字段password需要RSA加密。请求体原本是{username: test, password: mySecret123}。我们的目标是在Pre-request Script中将mySecret123加密。在确保forge库已加载的前提下我们编写加密函数// 定义RSA加密函数 function rsaEncrypt(plainText, publicKeyPem) { try { // 1. 从PEM字符串加载公钥 const publicKey forge.pki.publicKeyFromPem(publicKeyPem); // 2. 对明文进行加密 // forge默认使用RSAES-PKCS1-V1_5填充模式这是最常见的模式。 // 加密结果是二进制字节需要编码成可传输的格式这里用Base64。 const encryptedBytes publicKey.encrypt(plainText, RSAES-PKCS1-V1_5); const encryptedBase64 forge.util.encode64(encryptedBytes); return encryptedBase64; } catch (error) { console.error(RSA加密失败:, error); throw error; // 抛出错误以便在Postman控制台看到 } } // 在pm.sendRequest的回调中或确认forge已加载后执行加密 const plainPassword mySecret123; const publicKeyPem pm.environment.get(rsa_public_key); // 从环境变量获取公钥 const encryptedPassword rsaEncrypt(plainPassword, publicKeyPem); console.log(加密后的密码:, encryptedPassword); // 3. 将加密后的值更新到请求体中 // 首先获取当前的请求体假设是JSON let requestBody; try { requestBody JSON.parse(pm.request.body.raw); } catch(e) { requestBody {}; } // 更新password字段 requestBody.password encryptedPassword; // 将修改后的对象重新设置为请求体 pm.request.body.update({ mode: raw, raw: JSON.stringify(requestBody), options: { raw: { language: json } } });关键点解析填充模式RSAES-PKCS1-V1_5是RSA加密最常用的填充方案之一。有些后端可能使用OAEP填充。你必须与你的后端开发确认他们使用的填充模式。forge也支持RSA-OAEP只需更改encrypt方法的第二个参数即可。输出编码加密产生的是二进制数据直接放入JSON会出问题。Base64编码是网络传输的标准做法。后端在收到后需要先进行Base64解码再进行RSA解密。更新请求体pm.request.body.update()是动态修改请求体的核心方法。注意pm.request.body.raw在Pre-request阶段是只读的你不能直接赋值修改它必须通过update方法。4.2 RSA私钥解密实战当服务器返回加密数据时例如返回{data: Base64EncryptedString}我们需要在Tests脚本中进行解密。Tests脚本的执行顺序在收到响应之后。同样我们需要先确保forge可用可以复用Pre-request中设置的全局变量或者再加载一次。然后编写解密函数// 定义RSA解密函数 function rsaDecrypt(encryptedBase64, privateKeyPem) { try { // 1. 从PEM字符串加载私钥 const privateKey forge.pki.privateKeyFromPem(privateKeyPem); // 2. 将Base64字符串解码回二进制字节 const encryptedBytes forge.util.decode64(encryptedBase64); // 3. 使用私钥解密填充模式需与加密时一致 const decryptedBytes privateKey.decrypt(encryptedBytes, RSAES-PKCS1-V1_5); // 解密结果通常是字符串 const decryptedText decryptedBytes.toString(); return decryptedText; } catch (error) { console.error(RSA解密失败:, error); throw error; } } // 获取响应中的加密数据 const responseJson pm.response.json(); const encryptedDataFromServer responseJson.data; // 假设加密数据在data字段 // 获取私钥 const privateKeyPem pm.environment.get(rsa_private_key); // 执行解密 const decryptedData rsaDecrypt(encryptedDataFromServer, privateKeyPem); console.log(解密后的数据:, decryptedData); // 将解密结果存入环境变量或局部变量供后续断言使用 pm.environment.set(decrypted_secret_data, decryptedData); // 示例断言解密后的数据是否包含特定信息 pm.test(解密数据验证, function () { pm.expect(decryptedData).to.include(expectedKeyword); });注意事项填充模式一致性解密时的填充模式字符串必须与加密时完全一致否则会解密失败。错误处理加解密过程可能因为密钥格式错误、填充模式不匹配、数据损坏等原因失败。务必用try...catch包裹并在控制台输出错误信息这对于调试至关重要。密钥安全私钥用于解密敏感性更高。在团队协作中可以考虑将私钥变量设置为“初始值”Initial Value而不在当前值Current Value中显示。或者只在本地环境中配置私钥不同步到云端。5. 进阶应用与脚本优化5.1 处理更复杂的请求与响应结构现实中的API往往更复杂。加密的可能不是一个简单的字符串而是一个完整的JSON对象或者需要加密的字段嵌套在多层结构里。场景一加密整个对象。假设需要将{“cardNo”: “1234567890123456”, “idNo”: “110101199001011234”}这个对象整体加密后作为一个字段如encryptedData发送。// 在Pre-request Script中 const dataToEncrypt { cardNo: 1234567890123456, idNo: 110101199001011234 }; // 将对象转换为JSON字符串 const plainText JSON.stringify(dataToEncrypt); const encryptedBase64 rsaEncrypt(plainText, publicKeyPem); // 更新请求体可能整个请求体就是加密后的字符串或者是一个包含它的对象 let requestBody { version: 1.0, encryptedData: encryptedBase64, sign: ... }; pm.request.body.update({ mode: raw, raw: JSON.stringify(requestBody) });对应的在Tests中解密后你需要对解密出的字符串再进行一次JSON.parse才能得到原始对象。场景二动态加密请求中的多个字段。你可以编写一个通用的函数遍历请求体对象根据字段名规则例如字段名以_encrypt结尾来决定是否加密。function encryptRequestBody(obj, publicKeyPem) { for (let key in obj) { if (obj.hasOwnProperty(key) key.endsWith(_encrypt)) { // 找到需要加密的字段 const plainValue String(obj[key]); // 确保是字符串 obj[key] rsaEncrypt(plainValue, publicKeyPem); } else if (typeof obj[key] object obj[key] ! null) { // 递归处理嵌套对象 encryptRequestBody(obj[key], publicKeyPem); } } } // 使用 let requestBody JSON.parse(pm.request.body.raw); encryptRequestBody(requestBody, publicKeyPem); pm.request.body.update({ mode: raw, raw: JSON.stringify(requestBody) });5.2 解决ForgeJS异步加载的“顽疾”前面提到pm.sendRequest是异步的这可能导致加密逻辑在库加载完成前执行。一个可靠的解决方案是使用自执行函数和Promise来封装加载过程确保加密逻辑在库就绪后才执行。我们将加载逻辑封装成一个返回Promise的函数// 定义一个全局的forge加载Promise避免重复创建 if (!pm.globals.has(forgeLoadPromise)) { const forgeLoadPromise new Promise((resolve, reject) { if (typeof forge ! undefined) { resolve(forge); return; } const forgeUrl https://cdn.jsdelivr.net/npm/node-forge1.3.1/dist/forge.min.js; pm.sendRequest(forgeUrl, (err, response) { if (err) { reject(err); } else { eval(response.text()); pm.globals.set(forge, forge); // 缓存 resolve(forge); } }); }); pm.globals.set(forgeLoadPromise, forgeLoadPromise); } // 在需要加解密的地方使用async函数Postman脚本支持async/await (async () { try { const forge await pm.globals.get(forgeLoadPromise); // 现在forge库已确定加载完成可以安全使用了 const publicKeyPem pm.environment.get(rsa_public_key); const publicKey forge.pki.publicKeyFromPem(publicKeyPem); // ... 你的加密逻辑 ... const encrypted publicKey.encrypt(myPassword, RSAES-PKCS1-V1_5); const encryptedB64 forge.util.encode64(encrypted); // 更新请求体... let reqBody JSON.parse(pm.request.body.raw); reqBody.password encryptedB64; pm.request.body.update({ mode: raw, raw: JSON.stringify(reqBody) }); console.log(加密并更新请求体完成。); } catch (error) { console.error(脚本执行失败:, error); } })();这个模式通过Promise将异步加载“同步化”利用async/await让代码逻辑保持清晰直观彻底解决了加载时序问题。这是在实际项目中经过验证的稳定方案。5.3 集成到Postman Collection/Environment模板为了提高复用性避免在每个请求的脚本里重复写加载和加解密函数你可以这样做创建Collection级别的脚本在Collection的“Pre-request Scripts”和“Tests”标签页中写入forge加载的通用代码使用Promise方案以及rsaEncrypt和rsaDecrypt的函数定义。这样集合下的所有请求都能共享这些函数。在请求脚本中调用在每个具体请求的Pre-request Script中你只需要调用定义好的加密函数并更新当前请求的特定参数即可。代码会非常简洁。使用环境变量管理密钥这是最佳实践。将rsa_public_key和rsa_private_key定义在环境变量中方便在不同环境开发、测试、生产间切换。6. 常见问题与排查技巧实录在实际操作中你几乎一定会遇到下面这些问题。这里我把它整理成一张排查表方便你快速对照解决。问题现象可能原因排查步骤与解决方案Error: forge is not defined1. ForgeJS库未加载成功。2. 加密代码在库加载完成前执行。1. 检查CDN链接是否可访问。尝试在浏览器中打开https://cdn.jsdelivr.net/npm/node-forge1.3.1/dist/forge.min.js。2.采用上述的Promise加载方案确保代码执行顺序。3. 在Postman控制台查看pm.sendRequest的回调是否有错误。Error: Cannot read property ‘publicKeyFromPem’ of undefinedforge.pki对象不存在。通常是库加载不完整或加载的版本不对。1. 确认加载的是完整的forge库而不是部分模块。2. 尝试使用非minify版本forge.js进行调试。3. 在eval之后立即console.log(typeof forge, forge)查看对象结构。解密失败或加密后后端无法解密1.填充模式不一致。这是最常见的原因。2. 密钥不匹配非一对。3. 加密前的数据或解密后的编码问题。1.与后端确认填充模式。前端encrypt和decrypt的第二个参数必须与后端使用的模式一致PKCS1-v1_5 或 OAEP。2. 确保使用的公钥/私钥是配对的。用一把公钥加密必须用对应的私钥解密。3. 确认加密后的数据是否经过了正确的Base64编码并且后端在解密前进行了Base64解码。加载库时网络错误或超时Postman的pm.sendRequest受网络代理或防火墙限制。1. 检查Postman的代理设置Settings - Proxy。2. 尝试将forge.min.js代码本地化。将CDN文件内容复制保存为Postman的一个全局变量如forge_js_code然后在脚本中直接eval(pm.globals.get(‘forge_js_code’))。这是最稳定、最推荐的方法避免了网络依赖。PEM密钥格式错误1. 复制密钥时丢失了头尾标识或换行符。2. 密钥格式非forge支持的PKCS#8。1. 检查环境变量中的密钥字符串确保以-----BEGIN PUBLIC KEY-----开头以-----END PUBLIC KEY-----结尾中间有换行。2. 如果是PKCS#1格式的私钥-----BEGIN RSA PRIVATE KEY-----forge也能识别。公钥通常都是PKCS#8。加密大段数据时报错RSA加密有长度限制。2048位密钥最多加密245字节约。1. 不要直接用RSA加密过长的数据。常规做法是用RSA加密一个随机生成的AES密钥然后用这个AES密钥去加密实际数据。将RSA加密后的AES密钥和AES加密后的数据一起发送。这属于混合加密体系本教程聚焦RSA此场景可单独研究。Postman Web版脚本不工作Web版对eval和加载外部资源限制更严格。终极方案使用桌面版Postman。这是最根本的解决方案桌面版功能更完整限制更少。独家避坑技巧本地化ForgeJS代码这是保证稳定性的“杀手锏”。去CDN网站把forge.min.js的完整代码复制下来新建一个Postman全局变量比如叫LIB_FORGE_JS把代码贴进去。然后在Pre-request Script里只需要一行eval(pm.globals.get(‘LIB_FORGE_JS’));。从此彻底告别网络问题脚本执行速度也更快。使用console.log进行调试在关键步骤如加载库后、获取密钥后、加密前后都使用console.log输出关键变量注意不要打印完整的密钥。Postman的“Console”View - Show Postman Console是你的最佳调试伙伴。先单元测试再集成可以先在一个单独的请求里写一个简单的脚本只测试“加载forge - 用硬编码的密钥加密一个固定字符串 - 打印结果”这个流程。成功后再把逻辑迁移到真实的接口测试脚本中并换成从环境变量读取密钥。7. 完整示例一个用户登录接口的加密测试让我们用一个完整的例子串联所有步骤。假设登录接口POST /api/login要求对password字段进行RSA加密。1. 环境设置环境变量base_url:https://your-test-api.com环境变量rsa_public_key: 你的PEM格式公钥环境变量rsa_private_key: 你的PEM格式私钥用于解密测试响应如果响应不加密则不需要2. Collection级别的Pre-request Script可选但推荐// 将ForgeJS代码本地化存储于全局变量 forge_js_code 中后使用以下代码 // 加载并初始化forge if (typeof forge undefined) { try { eval(pm.globals.get(forge_js_code)); console.log(ForgeJS loaded from global variable.); } catch (e) { console.error(Failed to load ForgeJS:, e); } } // 定义通用加密函数 function rsaEncrypt(plainText, publicKeyPem) { const publicKey forge.pki.publicKeyFromPem(publicKeyPem); const encryptedBytes publicKey.encrypt(plainText, RSAES-PKCS1-V1_5); // 确认填充模式 return forge.util.encode64(encryptedBytes); }3. 具体请求/api/login的配置Method: POSTURL:{{base_url}}/api/loginBody(raw, JSON):{ username: testuser, password: PlainTextPasswordHere // 这个值将被脚本替换 }Pre-request Script:// 确保forge已加载 if (typeof forge undefined) { eval(pm.globals.get(forge_js_code)); } // 获取公钥 const publicKeyPem pm.environment.get(rsa_public_key); if (!publicKeyPem) { throw new Error(RSA公钥未在环境变量中设置 (rsa_public_key)); } // 获取当前请求体并加密password字段 const requestBody JSON.parse(pm.request.body.raw); const plainPassword requestBody.password; const encryptedPassword rsaEncrypt(plainPassword, publicKeyPem); // 更新请求体 requestBody.password encryptedPassword; pm.request.body.update({ mode: raw, raw: JSON.stringify(requestBody, null, 2) }); console.log(Password encrypted: ${plainPassword} - ${encryptedPassword.substring(0, 50)}...);Tests Script(假设响应是加密的data字段):// 解密响应如果需要 if (typeof forge undefined) { eval(pm.globals.get(forge_js_code)); } const privateKeyPem pm.environment.get(rsa_private_key); if (!privateKeyPem) { console.warn(未设置RSA私钥跳过解密验证。); } else { try { const responseJson pm.response.json(); const encryptedData responseJson.data; const privateKey forge.pki.privateKeyFromPem(privateKeyPem); const decryptedBytes privateKey.decrypt(forge.util.decode64(encryptedData), RSAES-PKCS1-V1_5); const decryptedText decryptedBytes.toString(); pm.environment.set(decrypted_login_info, decryptedText); console.log(Decrypted response data:, decryptedText); // 对解密后的数据进行断言 const decryptedObj JSON.parse(decryptedText); pm.test(Login successful, function () { pm.expect(decryptedObj.code).to.eql(0); }); } catch (decryptErr) { console.error(响应解密失败:, decryptErr); // 如果响应本就不该加密这里可以忽略错误 } } // 基础HTTP断言 pm.test(Status code is 200, function () { pm.response.to.have.status(200); });点击“Send”按钮你将在Postman Console中看到“Password encrypted…”的日志请求体中的password字段也变成了长长的Base64密文。如果后端接口正常你将收到响应并在Console中看到解密后的信息。至此你已经成功在Postman中构建了一个自动化的RSA加解密测试流程。这套方法可以无缝扩展到任何需要非对称加密的API测试场景大大提升了测试安全接口的效率和准确性。