一、序言
开发和测试同学平时多少都会用Postman进行后台接口调试,尤其是请求经过网关时,通常数据都需要进行加密传输,以及加签等操作。
这时候需要用到Pre-request和Post-response脚本,这篇文章将会详细介绍关于Postman这些脚本的小细节。
二、Pre-request预请求脚本语法
1、Pre-request脚本简介
通过名字就可以看出来,Pre-request脚本会在请求发送到服务端前执行,如下:
预请求脚本可以在Collections(集合)、Folder(文件夹)和API接口上定义,如果三者都定义了预请求脚本,那么执行顺序为:Collections > Folder > API接口。
备注:Postman运行环境基于
Node.js,因此相关JS语法可以无缝迁移。
2、Postman中的变量
Postman中支持各种作用域变量定义,支持的作用域有Global(全局)、Collection(集合)、Environment(环境变量)、Data和Local(脚本中本地变量),变量访问优先级从内到外,依次递增,如下:
通过pm内置对象可以访问各作用域的变量,如pm.gloabal或pm.environment等等,如下:
// collection var 'score' = 1
// environment var 'score' = 2
// first request run
console.log(pm.variables.get('score')); // outputs 2
console.log(pm.collectionVariables.get('score')); // outputs 1
console.log(pm.environment.get('score')); // outputs 2
// second request run
pm.variables.set('score', 3);// local var
console.log(pm.variables.get('score')); // outputs 3
// third request run
console.log(pm.variables.get('score')); // outputs 2
备注:通过
pm.variables对象获取变量时会根据上图中的优先级从里到外获取。
除此之外,Postman中还内置了一些动态变量,这些动态变量可以通过{{$guid}}花括号进行引用,如下:
在脚本中可以通过pm.variables.replaceIn()方法进行引用,如下:
const stringWithVars = pm.variables.replaceIn("Hi, my name is {{$randomFirstName}}");
console.log(stringWithVars);
3、Postman中的全局对象
Postman中支持常用的Javascript全局对象,可以看到大部分都支持。
除此之代,Postman还内置了一些三方库,比如moment.js、uuid.js等,使用内置三方库格式如下:
const moment = pm.require('moment');
const now = moment().format('YYYY-MM-DD HH:mm:ss');
console.log("当前时间: "+ now);
三、Pre-request脚本实现请求参数加密加签
目前前端常见的加解密库有CryptoJS、jsencrypt和jsrsasign,关于如何通过这些库实现对称加解密、非对称加解密&加验签的细节可以参考我之前的两篇文章:
- 前端CryptoJS和Java后端数据互相加解密(AES)
- 前后端RSA互相加解密、加签验签、密钥对生成
接下来的例子将以RSA非对称加密和加签为例,通过postman预请求脚本进行操作。
1、引入三方库
这里我使用了jsencrypt和jsrsasign两个库,首先定义了两个全局变量,变量的值为对应库的js脚本,如下:
通过全局变量获取到脚本内容后,通过eval()函数动态加载执行,就能获取到相关全局对象。
备注:这两个库的脚本可直接在
https://cdnjs.***/上搜索获取。
2、定义公私钥环境变量
开发、测试环境使用的公私钥都不一样,最好用环境变量进行区分,这里我定义了两个环境变量来存储公钥和私钥,如下:
备注:前端第三方库要求publicKey和privateKey的格式为标准pem格式,需要带
-----BEGIN PUBLIC KEY-----前缀。
3、预请求脚本实操
这里的预请求脚本可以作用在Collections、文件夹或者单个接口上,我一般全局定义在某个Collections上。
脚本内容如下:
/**
* RSA加密
*/
function encryptByRSA(publicKey, plainText) {
eval(pm.globals.get('jsencrypt'));
const encryptor = new window.JSEncrypt();
encryptor.setPublicKey(publicKey);
return encryptor.encrypt(plainText);
}
/**
* RSA SHA256加签
*/
function signBySHA256WithRSA(privateKey, msg) {
eval(pm.globals.get('jsrsasign'));
const key = KEYUTIL.getKey(privateKey);
const signature = new KJUR.crypto.Signature({
alg: "SHA256withRSA",
});
signature.init(key);
signature.updateString(msg);
// 签名后的为16进制字符串,这里转换为16进制字符串
return hextob64(signature.sign());
}
function sign(data) {
// 获取私钥
const privateKey = pm.environment.get('privateKey');
const signatureStr = signBySHA256WithRSA(privateKey, data);
console.log(`生成的签名为: ${signatureStr}`)
return signatureStr;
}
function encrypt() {
// 获取公钥
const publicKey = pm.environment.get('publicKey');
// 获取请求体
const requestBody = pm.request.body.raw;
if (!requestBody) {
return;
}
console.log("请求数据:" + requestBody);
try {
// 将请求体解析为JSON对象
const jsonData = JSON.parse(requestBody);
// 要加密的字段名 (替换为实际需要加密的字段名)
const encryptedFields = ["password", "encryptPwd", "newEncryptPwd", "paymentPin", "loginPwd"];
const filterFields = encryptedFields.filter(v => jsonData[v]);
if (!filterFields) {
return;
}
filterFields.forEach(field => {
// 加密指定字段
const plainText = jsonData[field];
const encryptedValue = encryptByRSA(publicKey, plainText)
console.log(`字段[${field}]加密后的内容为:${encryptedValue}`)
// 更新JSON中的值
jsonData[field] = encryptedValue;
// 将修改后的JSON转回字符串并更新请求体
pm.request.body.raw = JSON.stringify(jsonData);
})
return jsonData;
} catch (error) {
console.error("加密过程发生错误:", error);
}
}
// 定义全局变量
const navigator = {};
const window = {};
const jsonData = encrypt();
这里需要注意的是,脚本最后为什么要定义navigator和window两个全局变量,原因如下:
- JSEncrypt依赖于window和navigator对象,没有这两对象访问会失败。
- 前面说过Postman基于Node.js运行,而window和navigator是浏览器特有的对象。
备注:关于pm.request和pm.response等对象的数据结构可参考Postman变量结构说明。