接口测试 Jmeter 使用 beanshell 处理接口签名 (sign)

萧帅 · 2021年06月04日 · 最后由 萧帅 回复于 2021年11月05日 · 2855 次阅读

一、首页,了解接口签名规则

签名生成的通用步骤如下:

第一步:设所有发送的数据为集合 M,将集合 M 内非空参数值的参数按照参数名 ASCII 码从小到大排序(字典序),使用 URL 键值对的格式(即 key1=value1&key2=value2…)拼接成字符串 stringA。
特别注意以下重要规则:
◆ 参数名 ASCII 码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;(如空,或者 null 不参与签名,其他值需要参与签名)
◆ 参数名区分大小写;

第二步:在 stringA 最后拼接上 key 得到 stringSignTemp 字符串,并对 stringSignTemp 进行 MD5 运算,对得到的字符串转化为大写,得到 sign 值。

假设传送的参数如下:

{
"clientId": "ABAEE61B6A2629B598DF57D0F33E3F73",
"method": "qywUser_getUserById",
"id": 1,
"timeStamp": 1561693894,
"version":"1.0"
}

第一步:对参数按照 key=value 的格式,并按照参数名 ASCII 字典序排序如下:stringA=clientId=ABAEE61B6A2629B598D&id=1&method=qywUser_getUserById&timeStamp=1561693894&version=1.0

第二步:拼接客户端密钥:
stringSignTemp=stringA+&key=yrGewXbsp3weUSPcrma9wEN9T3d21P5Ue0S

md5 加密,然后转化成大写得到 sign:
String sign = DigestUtils.md5Hex(bsign).toUpperCase()=474464FA525E039E14E565C0AA179335

最终得到最终发送的数据:

{
"clientId": "ABAEE61B6A2629B598D",
"method": "qywUser_getUserById",
"id": 1,
"timeStamp": 1561693894,
"version":"1.0",
"sign":"474464FA525E039E14E565C0AA179335"
}

二、创建测试计划

新建线程组 → http 取样器 → 前置处理器 → bean shell 预处理程序

解决思路

a. 获取 Key 的值

b. 要提取并编辑 json 格式的内容, 需要用到处理 json 对象的工具包 fastjson

c. TimeStamp 虽然在签名内容之列,但这里不用提取,运行时给它传一个实时的就行

d. 按签名算法完成签名计算

e. 将签名和签名时用的 TimeStamp,替换到 request body 中

beanshell 代码如下:

import org.apache.commons.codec.digest.DigestUtils; 
import java.util.Date;
import org.apache.jmeter.config.*; 
import com.alibaba.fastjson.JSON;     // 文末有fastjson.jar的链接
import com.alibaba.fastjson.JSONObject;

Arguments args = sampler.getArguments();   // 截获请求,包含url、headers 和 body 三部分
Argument arg_body = args.getArgument(0);   // 获取请求body
String body = arg_body.getValue();         // 获取body的值保存成字符串
log.info(body);                            // 打印下看看,跑压测时勿忘把log注掉
JSONObject jso = JSON.parseObject(body);   // 把body转成json对象,注意!这里因为body本身就是json字符串,所以用json类处理,xml或其他格式的不能这样处理!!

String clientId = jso.getString("clientId");
String method = jso.getString("method");
String id= jso.getString("id");
String timeStamp = jso.getString("timeStamp");
String version = jso.getString("version");

//将签名传给cliSign参数,使用treemap,可自动进行排序
Map map = new TreeMap();
map.put("clientId", clientId);
map.put("method", method);
map.put("id", id);
map.put("timeStamp", timeStamp);
map.put("version", version);

//URLEncoder.encode(value, "UTF-8")   对中文进行格式化,这里不需要  
StringBuffer sb = new StringBuffer();
for (Map.Entry entry : map.entrySet()) {
    sb.append(entry.getKey() + "=" + entry.getValue());
    sb.append("&");
    }
    String s = sb.toString();
    if (s.endsWith("&")) {
        s = org.apache.commons.lang.StringUtils.substringBeforeLast(s, "&");
    }
    log.info("Map转换为URL编码"+s);

//将时间戳截取到秒的量级(长度共10位)
Date date = new Date();
String timestamp = String.valueOf(date.getTime()/1000);
String bsign = s + "&key=yrGewXbsp3weUSPcrma9wEN9T3d21P5Ue0S";
log.info("待加密字符串为:"+bsign);
String sign = DigestUtils.md5Hex(bsign); 
log.info("加密后的值:"+sign);

//替换 timestamp 和 sign 字段的值到jsonObject
jso.put("timeStamp",Integer.parseInt(timestamp));
jso.put("sign",sign.toUpperCase()); //md5加密,然后转化成大写得到sign

body = jso.toString();
arg_body.setValue(body); // 将新body替换到取样器的参数中,实现了截获 → 修改 → 发送修改后的内容

下载 fastjson 放到 jmeter 的 plugins 专用目录,如我的:
E:\DownLoad\Software\apache-jmeter-3.1\lib\ext

fastjson 下载地址:
https://github.com/alibaba/fastjson/releases

参考文档来源:
https://www.cnblogs.com/51benpao/p/13118078.html
https://www.cnblogs.com/z417/articles/13785978.html

共收到 3 条回复 时间 点赞

欢迎大佬指点,看看有没有最优方案参考,再次感谢!!

学习了,感谢

经过后续优化代码如下:
Set set=jso.keySet();//获取 json 对象所有的 key,返回一个 set
List strList= new ArrayList(set);//将 set 转换成 list
Collections.sort(strList);
StringBuffer strbuff=new StringBuffer();
for(String str:strList)//迭代所有的一级 key,并拼接好 key=value&的参数
{
if (str.equals("sign") || str.equals("Sign") || jso.getString(str)==null || "".equals(jso.getString(str))) {
continue;
}
strbuff.append(str);
strbuff.append("=");
strbuff.append(jso.getString(str));
strbuff.append("&");
}
String params=strbuff.toString();//得到拼接好的字符串
params=params.substring(0,params.length()-1);//去掉字符串最后的一个&符号
System.out.println(params);

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册