在用jmeter做接口的性能测试时,会遇到一些接口需要添加签名的要求,生成签名的过程需用对请求参数做排序,字符串拼接最后再用md5加密。
签名规则:
把公共参数和所有的业务参数,以key=value形式,按ASCII顺序排序并拼接起来,并在首尾加上appSercret值,然后使用MD5加密,得到签名,以capacity.plat.project.modify调用为例,具体步骤如下:
设置参数值
公共参数:
method=capacity.plat.project.modify
v=1.0.0
appId=ad0d25a5b165c6710792
accessToken=481D44D61172B88444543F745AEA552E
timestamp=2018-07-24 03:07:50
业务参数:
projectCode=5c3ae30bc5624ea49628a0b35926961c
projectName=test-02
modifyBy=limaob
accessToken=481D44D61172B88444543F745AEA552E
appId=ad0d25a5b165c6710792
method=capacity.plat.project.modify
modifyBy=limaob
projectCode=5c3ae30bc5624ea49628a0b35926961c
projectName=test-02
timestamp=2018-07-24 03:07:50
v=1.0.0
0a7f1dd284073c7eaccessToken481D44D61172B88444543F745AEA552EappIdad0d25a5b165c6710792methodcapacity.plat.project.modifymodifyBylimaobprojectCode5c3ae30bc5624ea49628a0b35926961cprojectNametest-02timestamp2018-07-24 03:07:50v1.0.00a7f1dd284073c7e
代码功能分析
示例请求:https://apicapacity.51iwifi.com/rest?sign=D1D514B61DFBD09E57BFD58CAE96C892&method=capacity.geye.device.list.get&v=1.0.0&appId=a29308b5716dc7f6b2ce×tamp=2019-10-17 19:38:40&accessToken=D184790A92240E6A8766AF0F269A7420&format=json¶ms={}
从签名规则里可以得出程序需要实现以下功能:
public static String getMD5(byte[] source) {
String s = null;
char hexDigits[] = { // 用来将字节转换成 16 进制表示的字符
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
md.update(source);
byte tmp[] = md.digest();
// MD5 的计算结果是一个 128 位的长整数,
// 用字节表示就是 16 个字节
char str[] = new char[16 * 2];
// 每个字节用 16 进制表示的话,使用两个字符,
// 所以表示成 16 进制需要 32 个字符
int k = 0;
// 表示转换结果中对应的字符位置
for (int i = 0; i < 16; i++) {
// 从第一个字节开始,对 MD5 的每一个字节
// 转换成 16 进制字符的转换
byte byte0 = tmp[i];
// 取第 i 个字节
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
// 取字节中高 4 位的数字转换,
// >>> 为逻辑右移,将符号位一起右移
str[k++] = hexDigits[byte0 & 0xf];
// 取字节中低 4 位的数字转换
}
s = new String(str).toUpperCase();
// 换后的结果转换为字符串
} catch (Exception e) {
e.printStackTrace();
}
return s;
}
拼接,排序字符等代码:
public static String makeOrder(String str,String appSecret) {
str=str.replace("=","");
String order = "";
String[] step1 = str.split("\\?|&");
step1[0]=step1[1]="";
Arrays.sort(step1);
for (String s : step1) {
order=order+s;
}
order=(appSecret+order+appSecret);
return order;
}
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author Cheertan
* @date 2021-04-28
*/
public class FileMd5 {
private final static String[] strHex = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
public static String getMD5One(String path) {
StringBuffer sb = new StringBuffer();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] b = md.digest(FileUtils.readFileToByteArray(new File(path)));
for (int i = 0; i < b.length; i++) {
int d = b[i];
if (d < 0) {
d += 256;
}
int d1 = d / 16;
int d2 = d % 16;
sb.append(strHex[d1] + strHex[d2]);
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
public static void main(String[] args) {
final byte[] buffer = new byte[1024];
File file = null;
MessageDigest md5 = null;
File f = new File("E:\\25.mp4");
if (f.exists() && f.isFile()) {
System.out.println(f.length());
} else {
System.out.println("not exists");
}
System.out.println(getMD5One("E:\\25.mp4"));
Date date = new Date();
System.out.println(date.toString());
String beforeMd5 = date.toString()+"25.mp4";
System.out.println(beforeMd5);
}
}
在开发工具如IDEA,eclipse上实现该代码后,将代码打包成jar,jmeter支持调用jar,这里发现在jmeter里只能每次调用一个类里的一个方法,makeOrder和MD5方法分为两个类进行打包成jar。
在测试计划处添加要导入的jar包:
签名规则里需要用到accessToken,该token需要调用一个接口获得,将返回值中的token取出设置为环境变量以备下个接口调用。
在线程组中加入http请求,注意在箭头处,参数里包含特殊字符如空格,花括号的,需要勾上URL Encode
在发送请求后需要提取出返回数据中的accessToken值,这里用到里jmeter的正则提取器,切记需要在请求上方右击添加正则提取器
添加完正则提取后来看看,具体怎么提取:
返回数据为:{"data":{"accessToken":"D184790A92240E6A8766AF0F269A7420","expiresIn":604800,"refreshToken":"7475CFB5332817C12529453845EDC443","scope":"default","tokenType":"bearer"},"errorCode":"0"}
核心在JSON path处填入:$.data.accessToken
更多使用教程点我
下面是核心步骤
在Jmeter上调用请求时大概分为两类:
Jmeter不便之处在于填了params就不能写Body Data,遇见一些接口两个都需要时就十分尴尬,这里的思路上把放在params的参数写成url放入环境变量里,这里会涉及到url里特殊字符的编码处理
import MD5;
import makeOrder;
String appSecret=vars.get("appSecret");
String sign="//apicapacity.51iwifi.com/rest?sign=&method=capacity.geye.device.devUrl.get &v=1.0.0&appId=a29308b5716dc7f6b2ce×tamp="+vars.get("timestamp")+"&accessToken="+vars.get("accessToken")+"&format=json¶ms="+vars.get("params");
sign=Order.makeOrder(sign,appSecret);
sign=MD5.getMD5(sign.getBytes());
vars.put("sign",sign);
代码中先是将url进行字符拼接,赋值给sign,再调用Order中的makeOrder方法,根据签名规则进行处理,处理好的字符再交给MD5中的getMD5方法处理,将处理好的sing值最后放到环境变量sign中,此时只要在请求parameters处调用环境变量sign即可。
注意在发送http请求时加上请求头部的内容类型设置,不然网站后台会无法解析发送数据
由于jmeter的特殊情况,这里只能把添加parameters后的url先写出来,再对一些特殊字符进行编码转义,最后传到url变量里,在请求处直接调用url,而不是像get请求一样一个个在parameters处填写参数,让jmeter自己拼接参数和编码。
import MD5;
import makeOrder;
String appSecret=vars.get("appSecret");
String requestBody = sampler.getArguments().getArgument(0).getValue();//获取请求中的body
String sign="/rest?sign=&method=capacity.geye.device.add&v=1.1.1&appId=a2ce701cb16d70cff5d3×tamp="+vars.get("timestamp")+"&accessToken="+vars.get("accessToken")+"&format=json¶ms="+vars.get("params");
sign=sign+"&bodyContent="+requestBody;
sign=Order.makeOrder(sign,appSecret);
vars.put("temp",sign);
sign=MD5.getMD5(sign.getBytes());
String url="/rest?sign="+sign+"&method=capacity.geye.device.add&v=1.1.1&appId=a2ce701cb16d70cff5d3×tamp="+vars.get("timestamp")+"&accessToken="+vars.get("accessToken")+"&format=json¶ms="+vars.get("encodeParams");
vars.put("sign",sign);
vars.put("requestBody",requestBody);
vars.put("url",url);
代码中先将请求体和请求进行拼接,拼接后进行排序和MD5加密,这只是单纯的算出签名,如果你想拿这个url发出,jmeter会提示你特殊字符错误,这就要对请求中的特殊符号如空格,花括号进行编码处理,jmeter集成了urlencode的函数,只需要调用就行了
将生成的${__urlencode({"something:" "your params"})}
填入环境变量
最后在代码处调用即可。
jmter官方文档
java完整代码:
MD5:
public class MD5 {
public static String getMD5(byte[] source) {
String s = null;
char hexDigits[] = { // 用来将字节转换成 16 进制表示的字符
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
md.update(source);
byte tmp[] = md.digest();
// MD5 的计算结果是一个 128 位的长整数,
// 用字节表示就是 16 个字节
char str[] = new char[16 * 2];
// 每个字节用 16 进制表示的话,使用两个字符,
// 所以表示成 16 进制需要 32 个字符
int k = 0;
// 表示转换结果中对应的字符位置
for (int i = 0; i < 16; i++) {
// 从第一个字节开始,对 MD5 的每一个字节
// 转换成 16 进制字符的转换
byte byte0 = tmp[i];
// 取第 i 个字节
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
// 取字节中高 4 位的数字转换,
// >>> 为逻辑右移,将符号位一起右移
str[k++] = hexDigits[byte0 & 0xf];
// 取字节中低 4 位的数字转换
}
s = new String(str).toUpperCase();
// 换后的结果转换为字符串
} catch (Exception e) {
e.printStackTrace();
}
return s;
}
}
makeOrder代码:
public class Order {
public static String makeOrder(String str,String appSecret) {
str=str.replace("=","");
String order = "";
String[] step1 = str.split("\\?|&");
step1[0]=step1[1]="";
Arrays.sort(step1);
for (String s : step1) {
order=order+s;
}
order=(appSecret+order+appSecret);
return order;
}
}
在jmter中获取request body值:String requestBody = sampler.getArguments().getArgument(0).getValue();
获取request url:String url=sampler.getUrl().toString();