作者:Mars酱
声明:文章由作者原创,欢迎转载,转载前请联系我!
公司准备做压力测试,业务场景是模拟1000个用户访问服务器接口,以检测自己的卡片是否真实有效。由于服务器端是做了鉴权校验的,只允许发放了密钥对的用户在做好签名编码后,才可以检查卡片是否真实有效。因此我们在发送请求前要通过密钥对做好签名,最后发送给服务端做鉴权校验和卡片检查。
后端签名的方式是把所有参数按顺序拼接好,最后再拼接上密钥的加密key,把整个参数字符串做md5编码,便可以得到签名串sign,后端检测收到的参数和服务端自行加密签名得到的签名串一致,则允许通过鉴权,并做后续的卡片检查业务。
后端签名校验逻辑(请忽略代码规范和性能,目的只为解释后端逻辑),如下:
// 获取客户端发送的请求参数
// accessKeyId
String accessKeyId = request.getParams("accessKeyId");
// 卡号
String cardNo = request.getParams("cardNo");
// 随机数
String nonceStr = request.getParams("nonceStr");
// 时间戳
String timestamp = request.getParams("timestamp");
// 通过客户端accessKeyId找到发放给客户端的加密密钥
String key = getKey(accessKeyId);
// 所有参数按照顺序拼接好
String params = "accessKeyId=“ + accessKeyId;
params += ”&cardNo=" + cardNo;
params += ”&nonceStr=" + nonceStr;
params += ”×tamp=" + timestamp;
// 最后拼接上key
params += "&key=" + key;
// 再做md5编码, 得到签名
String serverSign = md5(params);
System.out.println(“sign=” + sign);
// 输出如下:
// sign=736E3E80E70CDB3246C28664739452EC
// 拿到客户端传递的sign签名串
String clientSign = request.getParams("sign");
// 把服务端的签名sign和客户端的对比,如果相等,则处理后续业务,否则为非法访问!
if(serverSign.equals(clientSign)){
// ... 校验卡片信息是否真实有效
}
客户端发送的请求参数需要与服务端参数拼接顺序一致,如下:
https://www.domain.com/api/card_external/cardinfo/check?accessKeyId=6aYkjYFJ&cardNo=1000000002070270&nonceStr=nNEaWSmTMDZrZkin×tamp=1678365035148&sign=736E3E80E70CDB3246C28664739452EC
压测主要模拟客户端请求服务端,作为一个java开发首选肯定是java派系测试工具
JMeter版本:Apache JMeter 5.5
JDK:OpenJDK 16
由于模拟客户端发送请求,因此测试前需要根据业务场景的需求提前准备以下数据:
以上5种数据,我们需要在JMeter中对应的准备好。根据java编码方式,所有参数应该都是变量,最后在发送get请求的时候引用变量并发送给接口
既然发送http的请求,因此我们先添加http请求的配置节点,选择菜单如下:
在“HTTP请求”节点的配置界面填写上请求地址信息,主要配置页面需要填写以下红框中的内容,如下:
协议: 在协议部分写清楚服务端使用的是什么方式,我需要请求的目标服务器是https协议,因此我填写了https。
服务器名称或者ip: 写上自己服务器的ip地址+port端口号,有域名的写上完整域名。我是有域名的,因此只要写域名即可。
HTTP请求方式: 我的业务场景主要是GET请求,因此选择GET
路径: 这里需要注意,一个完整的接口应该由域名 + 上下文路径 + 请求参数组成,如下:
https://www.domain.com/api/card_external/cardinfo/check
https://www.domain.com
是域名
api/card_external/cardinfo/check
是上下文路径
而且get请求一般都是直接在接口后面直接跟参数,如:
http://www.domain.com/api/card_external/cardinfo/check?key1=value1&key2=value2&key3=value3
由于是get请求,因此,路径部分应该在get请求中是上下路径 + 请求参数,如:
api/card_external/cardinfo/check?key1=value1&key2=value2&key3=value3
在平时的开发工作中key和value都应该是变量填充,因此我们通过JMeter测试工具也应该是模拟去填充值,所以,红框中的内容我们填写如下:
api/card_external/cardinfo/check?accessKeyId=${accessKeyId}&cardNo=${carno}&nonceStr=${nonceStr}×tamp=${timestamp}&sign=${sign}
以上路径中主要引入了以下变量 ${accessKeyId}
${cardno}
${nonceStr}
${timestamp}
${sign}
,因为我们模拟不同的用户,所以这几个参数的值是会变化的。
而变量名使用${...}
符号方式包裹,是因为JMeter的使用规则。
配置好HTTP请求和引入变量之后,我们就需要声明这些变量,变量的声明在JMeter中是通过“用户定义的变量”节点去声明的,添加好节点:
在配置界面声明好变量,此处的变量名需要与“HTTP请求”配置中引入的变量名一致,如下:
在此,我们只声明了四个变量accessKeyId
nonceStr
timestamp
sign
,其中,除了accessKeyId
变量的值固定写死,其余的变量值都是随便写,因为这几个变量在后面会需要重新赋值的。
细心的朋友也会发现${cardno}
变量没有在此声明。那是由于我们需要模拟1000个用户,每个用户1张卡,因此我们需要有卡池,卡池和${cardno}
变量的声明往下翻。
JMeter支持csv格式文件的读取,因此我们把卡池数据存放在csv文件中。
卡池准备好了,那么我们就需要完成${cardno}
变量的声明了,选择“配置元件”中的“CSV Data Set Config”:
然后在“CSV 数据文件设置”菜单中配置,如下:
文件名: 选择卡池csv文件的本地存放路径;
变量名称(细纹逗号间隔): 填写变量名cardno,此处与HTTP请求配置中cardNo=${cardno}的变量必须对应。
其余的配置使用默认选项。
HTTP请求的变量引入了,引入的变量也声明了,卡池也准备好,卡片变量名也声明了,那么现在就在在发送请求之前做好最后一步工作:给所有变量名赋值!
我们需要赋值的变量名已经在“HTTP请求”配置引入,主要是: ${accessKeyId}
${cardno}
${nonceStr}
${timestamp}
${sign}
在JMeter中赋值操作主要使用“BeanShell取样器”来完成,取样器中是需要编写java代码的,因此我们先来添加“BeanShell取样器”节点:
在打开的配置界面中编写好java代码,完成赋值过程,截图如下:
核心来了!BeanShell中的java代码如下:
/** author: mars酱 */
import org.apache.commons.codec.digest.DigestUtils;
import java.util.Date;
// 1. 取变量名,不是取url的key值
String accessKeyId = vars.get("accessKeyId");
// 2. 取卡号
String cardNo = vars.get("cardno");
// 3. 随机数
String nonceStr = vars.get("nonceStr");
nonceStr = "";
String chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz";
int maxLen = chars.length();
for (int i = 0; i < 16; i++) {
double floor = Math.floor(Math.random() * maxLen);
int intValue = (new Double(floor)).intValue();
nonceStr += String.valueOf(chars.charAt(intValue));
}
vars.put("nonceStr", nonceStr);
// 4. 时间戳
String timestamp = new String();
Date date = new Date();
timestamp = String.valueOf(date.getTime());
vars.put("timestamp", timestamp);
// 5. 密钥
String key = "e992fdbec7cd1eb2b1c6c8e07d4eea60";
// 6. 拼接待签名的参数值
String params = "accessKeyId=" + accessKeyId + "&cardNo=" + cardNo + "&nonceStr=" + nonceStr + "×tamp=" + timestamp + "&key=" + key;
log.info("拼接后的参数:{}", params);
String sign = "";
// 7. md5签名整个参数
sign = DigestUtils.md5Hex(params).toUpperCase();
vars.put("sign", sign);
BeanShell取样器的java代码中有vars变量,这个是JMeter的内置变量,会在最后一节简单解释。
ok,http请求的所有配置赋值工作到此已经完成,但是在发送请求之前,我们还需要监听发送之后的响应结果,因此我们还需要添加监听器
监听器的作用是监听响应结果,主要添加三种监听器节点,也可以根据自己的情况适当选择,我主要添加了:
到此,所有的请求配置、请求中变量赋值、卡池数据、监听结果报告都已经配置完成。最后,在线程组中设置好需要测试用的线程数、Ramp-Up时间等参数后,点击顶部“”按钮,开始运行
三个不同的监听器通过不同的维度展示整个压测的结果
“BeanShell取样器”相当于ide的调试,可以查看到编写的java脚本是否有错误,飘绿说明正确,可以通过切换“取样器结果”、“请求”、“响应数据”三个不同的选项卡查看自己需要的结果;
“调试取样器”是对“BeanShell取样器”的监听,主要是看是否在“BeanShell取样器”的编译结果是否正确,需要添加“调试取样器”节点
选择“查看结果树”中的“调试取样器”可以查看结果,主要查看“响应数据”,其结果是“BeanShell取样器”生成的调试结果:
“查看结果树”中主要看“HTTP请求”选项的结果:
选择“请求”选项卡,可以看到发送服务端的完整请求地址和填充好的变量值:
还有服务端的响应结果:
到此,使用JMeter模拟发送get请求已经完成,主要使用JMeter内置变量完成参数填充。
下面简单提两句内置变量:
vars和props都是jmeter的内置变量,不必先定义,可以直接使用。
1.vars 只能在当前线程组使用(局部),props 可以跨线程组使用(全局)
2.vars 只能保存 String 或 Object ,props 是 Hashtable 对象
搬运自己掘金:JMeter | BeanShell入门级编写