目录
一、前言
有些接口的请求会带上sign
(签名)进行请求,各接口对sign
的签名内容、方式可能不一样,但一般都是从接口的入参中选择部分内容组成一个字符串,然后再进行签名操作, 将结果赋值给sign
; 完整规范的接口文档都会有sign
的算法描述。
这里通过Postman
的Pre-request Script
以及JMeter
的BeanShell
前置处理器进行接口签名的处理。
(完整代码在每部分的最后)
被测系统teachSignServer
:
Gitee:https://gitee.com/z417/knowledgebroadcast/tree/master/teachSignServer-tools
直接双击运行.exe
文件即可(密钥文件与conf.ini
需要与exe
处于同一文件夹下)
其余工具:
1.bundle.js:https://github.com/joolfe/postman-util-lib/tree/master/postman
使用方式我们在后面使用到了再进行介绍
2.json.jar: https://mvnrepository.com/artifact/org.json/json
选择适合的版本
点击bundle
将下载的jar
包置于jmeter
的./lib/ext
下并重启jmeter
被测接口信息:
接口 | URL | Method | Body | 签名规则 |
v0 | http://127.0.0.1:5000/api/v0/teachsign | POST | { "AppKey": "z417App", "AppVer": "1.0.0", "Data": "{"SPhone":"18662255783","EType":0}", "DeviceName": "web", "DeviceType": "web", "Lang": "CN", "Sign": "teachsign", "TimeStamp": 1625456804 } | appkey,timestamp,data,secret四个字段的值拼接,使用32位md5进行签名 |
v1 | http://127.0.0.1:5000/api/v1/teachsign | POST | { "appid": "wxd930u", "mch_id": 10100, "device_info": 100, "body": "{"EType":0}", "DeviceType": "", "nonce_str": "ibuaiVc", "sign": "CD198C36632A274C49E5F2F028FA257C", "source": null, "ts": 1625456804 } | 1. 参与签名运算的参数选用入参里边value非空的参数 2. 参与签名运算的参数按照ASCII顺序排序 3. 组合方式:key=value通过&符连接 4. 最后加上盐key=secret(secret在conf.ini中配置,同理后面的私钥与公钥也可在其中进行配置) 5. 使用32位md5进行签名,sign的字母全大写 |
v2 | http://127.0.0.1:5000/api/v2/teachsign | POST | { "busId": "", "busCnl": "POS", "requJnINo": "abceefgghkjlafksdffdsf", "reqTxnTm": "16:30:16", "serviceCode": "chengxusong", "bussJnIno": "Arabic - Bahrain", "sign": "fsdfsd", "reqTxnDt": "20210907", "nonceStr": "Language", "sysCnl": "OKPOS", "ts": 1631003416 } | 1. 参与签名运算的参数选用入参里边value非空的参数 2. 参与签名运算的参数按照ASCII顺序排序 3. 使用private_key签名 4. 使用SHA256withRSA进行签名 |
二、v0接口
1.0 Postman
获取请求参数并将body
的参数转换为json
对象
var Json = JSON.parse(pm.request.body);
获取所需变量并将新的时间戳更新到json
对象中
var TimeStamp = Date.parse(new Date()) / 1000 - 10;
Json.TimeStamp = TimeStamp;
var AppKey = Json.AppKey;
var Data = Json.Data;
var secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63";
字符串拼接
var str = AppKey + TimeStamp + Data + secretKey;
进行md5
运算并将生成的hash
序列转换为字符串
var strmd5= CryptoJS.MD5(str).toString();
修改json
对象中sign
并将md5
对象写回body
中
Json.Sign = strmd5;
pm.request.body.raw = JSON.stringify(Json); // 将修改后的JSON转换回字符串格式写回到请求体中
完整代码:
/*
vo加密规则:
appkey,timestamp,data,secret四个字段的值拼接,使用32位md5加密
*/
/*
* 获取请求参数
*/
var Json = JSON.parse(pm.request.body); // 将body的参数转换为json对象
/*
* 获取所需变量
*/
var TimeStamp = Date.parse(new Date()) / 1000 - 10; // 获取时间戳
Json.TimeStamp = TimeStamp; // 修改JSON
var AppKey = Json.AppKey;
var Data = Json.Data;
var secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63";
/*
* 拼接字符串并加密
*/
var str = AppKey + TimeStamp + Data + secretKey;
var strmd5= CryptoJS.MD5(str).toString(); // 调用方法进行md5运算并将生成的hash序列转换为字符串
Json.Sign = strmd5; // 修改JSON
pm.request.body.raw = JSON.stringify(Json); // 将修改后的JSON写回到请求体中
2.0 JMeter
在JMeter
的时间戳可以直接使用JMeter
自带函数在body
中获取,当然也可以在BeanShell
前置处理器中使用代码获取/1000
是因为JMeter
默认生成的时间戳为13位时间戳,我们只需要10位即可。
导包(org.json
为第三方jar
包)
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*;
获取请求传入的body
,将其转化为Json
对象
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue());
获取变量并拼接
String TimeStamp = dataobj.optString("TimeStamp");
String AppKey = dataobj.optString("AppKey");
String Data = dataobj.optString("Data");
String secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63";
String str = AppKey + TimeStamp + Data + secretKey;
进行md5
运算
sign = DigestUtils.md5Hex(str);
修改json
对象的sign
并转换为字符串写回body
中
dataobj.put("Sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中
完整代码:
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*;
/*
* 获取请求传入的body,将其转化为Json对象
*/
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue());
/*
* 获取变量并拼接字符串
*/
// 获取变量
String TimeStamp = dataobj.optString("TimeStamp");
String AppKey = dataobj.optString("AppKey");
String Data = dataobj.optString("Data");
String secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63";
// 字符串拼接
String str = AppKey + TimeStamp + Data + secretKey;
/*
* 签名,更新body
*/
sign = DigestUtils.md5Hex(str); // md5运算
dataobj.put("Sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并写回request-body中
三、v1接口
1.0 Postman
获取请求参数并将body
的参数转换为json
对象
var Json = JSON.parse(pm.request.body);
获取时间戳并修改json
对象
var ts = Date.parse(new Date()) / 1000 - 10;
Json.ts = ts;
去除sign
参数本身,然后去除值是空的参数
var keys = [];
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
}
排序
keys.sort();
拼接字符串
let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63";
进行md5
运算并将生成的hash
序列转换为字母全大写字符串
var strmd5= CryptoJS.MD5(keys_str).toString().toUpperCase();
修改json
对象中sign
并将md5
对象写回body
中
Json.sign = strmd5;
pm.request.body.raw = JSON.stringify(Json);
完整代码:
/*
v1加密规则:
1. 参与签名运算的参数选用入参里边value非空的参数
2. 参与签名运算的参数按照ASCII顺序排序
3. 组合方式:key=value通过&符连接
4. 最后加上key=secret
5. 使用32位md5签名,sign的字母全大写
*/
/*
* 获取请求参数
*/
var Json = JSON.parse(pm.request.body);
var ts = Date.parse(new Date()) / 1000 - 10; // 获取时间戳
Json.ts = ts; // 修改json
/*
* 去除sign参数本身,然后去除值是空的参数
*/
var keys = []; // 定义key序列
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
}
/*
* 对请求参数排序
*/
keys.sort();
/*
* 拼接字符串
*/
let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63";
/*
* 签名并更新body
*/
var strmd5= CryptoJS.MD5(keys_str).toString().toUpperCase(); // 调用方法进行md5运算并将生成的hash序列转换为字母全大写字符串
Json.sign = strmd5; // 修改Json
pm.request.body.raw = JSON.stringify(Json);
2.0 JMeter
同样在body
中使用内置函数定义时间戳ts
导包
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*;
获取请求传入的body
,将其转化为Json
对象
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue());
获取Json
的key
// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
}
}
对list
进行排序
Collections.sort(keyArry);
字符串拼接
String str = "";
for (String s : keyArry) {
// log.error(s);
String value = dataObj.optString(s);
// 剔除值为空或值为null的参数
if (!value.equals("") && !value.equals(null)) {
str = str+s+"="+ value+"&";
}
}
str = str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63";
进行md5
运算并转换为字母全大写
String sign = DigestUtils.md5Hex(str).toUpperCase();
修改json
对象的sign
并转换为字符串写回body
中
dataobj.put("sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中
完整代码:
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*;
/*
* 获取请求传入的body,将其转化为Json对象
*/
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataObj = new JSONObject(arg.getValue());
/*
* 获取Json的key进行排序
*/
// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
}
}
/*
* 对list进行排序
*/
Collections.sort(keyArry);
/*
* 循环list中的key,读取对应的Value组成字符串
*/
String str = "";
for (String s : keyArry) {
String value = dataObj.optString(s);
// 剔除值为空或值为null的参数
if (!value.equals("") && !value.equals(null)) {
str = str+s+"="+ value+"&";
}
}
str = str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63";
/*
* 签名并更新body
*/
String sign = DigestUtils.md5Hex(str).toUpperCase(); // 进行md5运算并转换为字母全大写
dataobj.put("sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并写回request-body中
四、v2接口
1.0 Postman
将下载后的json
导入到postman
,进入Lib install
请求
发送请求,该请求会将bundle.js
写入到全局变量中
获取请求参数并将body
的参数转换为json
对象
var Json = JSON.parse(pm.request.body);
获取时间戳并修改json
对象
var ts = Date.parse(new Date()) / 1000 - 10;
Json.ts = ts;
去除sign
参数本身,然后去除值是空的参数
var keys = [];
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
}
排序
keys.sort();
拼接字符串
let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str.slice(0,-1); // 删除最后一个&
导入刚才写入到全局变量的js
eval(pm.globals.get("pmlib_code"));
由于私钥过长,所以这里把私钥的内容写到环境变量中,私钥内容可在pkcs8_rsa_private_key.pem
查看(私钥与公钥可自行更换,在conf.ini
中进行配置即可)
获取私钥
const privatekey = pm.environment.get("privatekey");
加密
const sha256withrsa = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA256withRSA"}); // 生成签名对象并制定为SHA256withRSA类型
sha256withrsa.init(privatekey); // 初始化privatekey
sha256withrsa.updateString(keys_str); // 更新要签名的数据
const sign = pmlib.rs.hextob64(sha256withrsa.sign()); // 签名并转换为Base64字符串
修改json
对象中sign
并将md5
对象写回body
中
Json.sign = sign;
pm.request.body.raw = JSON.stringify(Json); // 将修改后的JSON转换回字符串格式写回到请求体中
完整代码:
/*
v2加密规则:
1. 参与签名运算的参数选用入参里边value非空的参数
2. 参与签名运算的参数按照ASCII顺序排序
3. 使用private_key签名
4. 使用SHA256withRSA进行签名
*/
/*
* 获取请求参数
*/
var Json = JSON.parse(pm.request.body);
var ts = Date.parse(new Date()) / 1000 - 10; // 获取时间戳
Json.ts = ts; // 修改json
/*
* 去除sign参数本身,然后去除值是空的参数
*/
var keys = []; // 定义key序列
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
}
/*
* 对请求参数排序
*/
keys.sort();
/*
* 拼接字符串
*/
let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str.slice(0,-1); // 删除最后一个&
/*
* 加密并更新body
*/
eval(pm.globals.get("pmlib_code")); // 导入写入到全局变量的js
const privatekey = pm.environment.get("privatekey"); // 从环境变量获取私钥
const sha256withrsa = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA256withRSA"}); // 生成签名对象并制定为SHA256withRSA类型
sha256withrsa.init(privatekey); // 初始化privatekey
sha256withrsa.updateString(keys_str); // 更新要签名的数据
const sign = pmlib.rs.hextob64(sha256withrsa.sign()); // 签名并转换为Base64字符串
Json.sign = sign;
pm.request.body.raw = JSON.stringify(Json);
2.0 JMeter
同样在body
中使用内置函数定义时间戳ts
,同时添加用户定义的变量配置元件来存放私钥
导包
import org.apache.jmeter.config.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
获取请求传入的body
,将其转化为Json
对象
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue());
获取Json
的key
// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
}
}
对list
进行排序
Collections.sort(keyArry);
字符串拼接
String str = "";
for (String s : keyArry) {
// log.error(s);
String value = dataObj.optString(s);
// 剔除值为空或值为null的参数
if (!value.equals("") && !value.equals(null)) {
str = str+s+"="+ value+"&";
}
}
//删除最后一个&
str = str.substring(0,str.length()-1);
读取私钥
java中读取私钥需要删除前面的“-----BEGIN PRIVATE KEY-----
”和后面的“-----END PRIVATE KEY-----
”,且需要key
首尾连接中间无换行或空格。
String privateKeyString = vars.get("privateKey"); // 从用户定义的变量中读取私钥
privateKeyString = privateKeyString.replace(" ", ""); // 删除多余的空格
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString); // 将Base64解码转化为字符串
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); // 根据给定的编码密钥创建一个新的 PKCS8EncodedKeySpec
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec); // 生成RSA的私钥对象。
创建 Signature
对象并初始化
Signature signature = Signature.getInstance("SHA256withRSA"); // 生成SHA256withRSA的Signature 对象
signature.initSign(privateKey); // // 初始化签署签名的私钥
更新要签名的数据
signature.update(str.getBytes("UTF-8")); // 更新要签名或验证的字节
签名
byte[] signatureBytes = signature.sign(); // 执行签名
String sign = Base64.getEncoder().encodeToString(signatureBytes); // 将签名结果转换为 Base64 字符串
修改json
对象的sign
并转换为字符串写回body
中
dataobj.put("sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中
完整代码:
import org.apache.jmeter.config.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
/*
* 获取请求传入的body,将其转化为Json对象
*/
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataObj = new JSONObject(arg.getValue());
/*
* 获取Json的key进行排序
*/
// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
// log.error(key);
}
}
// 对list进行排序
Collections.sort(keyArry);
/*
* 循环list中的key,读取对应的Value组成字符串
*/
String str = "";
for (String s : keyArry) {
// log.error(s);
String value = dataObj.optString(s);
if (!value.equals("")) {
str = str+s+"="+ value+"&";
}
}
//删除最后一个&
str = str.substring(0,str.length()-1);
/*
* 读取私钥
*/
String privateKeyString = vars.get("privateKey"); // 从用户定义的变量中读取私钥
privateKeyString = privateKeyString.replace(" ", ""); // 删除多余的空格
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString); // 将Base64解码转化为字符串
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); // 根据给定的编码密钥创建一个新的 PKCS8EncodedKeySpec
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec); // 生成RSA的私钥对象。
/*
* 读创建 Signature 对象并初始化
*/
Signature signature = Signature.getInstance("SHA256withRSA"); // 生成SHA256withRSA的Signature 对象
signature.initSign(privateKey); // 初始化签署签名的私钥
//
/*
* 更新要签名的数据化
*/
signature.update(str.getBytes("UTF-8")); // 更新要签名或验证的字节
/*
* 签名并更新body
*/
byte[] signatureBytes = signature.sign(); // 签署所有更新字节的签名
// 将签名结果转换为 Base64 字符串
String sign = Base64.getEncoder().encodeToString(signatureBytes); // 编码为Base64字符串
dataobj.put("sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中
评论记录:
回复评论: