Jmeter字符串MD5和文件MD5生成签名

功能分析

在用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
  1. 按ASCII顺序排序
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
  1. 拼接参数名与参数值,在首尾加上appSercret(以值为0a7f1dd284073c7e为例)
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={}
从签名规则里可以得出程序需要实现以下功能:

  • 去除参数中的等于号
  • 字符串分割,提取出url中的参数部分(sign的字段需要去掉)
  • 根据参数名进行排序
  • 将重新排序后的字符串前后拼接上appSecret
  • 将拼接好的字符串做md5运算并把结果转为大写字符

实现

Java代码编写

字符串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;
    }

拼接,排序字符等代码:

 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;
    }
文件md5加密
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。

jmeter调用实现

在测试计划处添加要导入的jar包:
Jmeter字符串MD5和文件MD5生成签名_第1张图片
签名规则里需要用到accessToken,该token需要调用一个接口获得,将返回值中的token取出设置为环境变量以备下个接口调用。
在线程组中加入http请求,注意在箭头处,参数里包含特殊字符如空格,花括号的,需要勾上URL Encode
Jmeter字符串MD5和文件MD5生成签名_第2张图片
在发送请求后需要提取出返回数据中的accessToken值,这里用到里jmeter的正则提取器,切记需要在请求上方右击添加正则提取器
Jmeter字符串MD5和文件MD5生成签名_第3张图片
添加完正则提取后来看看,具体怎么提取:
返回数据为:{"data":{"accessToken":"D184790A92240E6A8766AF0F269A7420","expiresIn":604800,"refreshToken":"7475CFB5332817C12529453845EDC443","scope":"default","tokenType":"bearer"},"errorCode":"0"}
Jmeter字符串MD5和文件MD5生成签名_第4张图片
核心在JSON path处填入:$.data.accessToken
更多使用教程点我
下面是核心步骤
在Jmeter上调用请求时大概分为两类:

  • 带body和params的请求
  • 只带params的请求

Jmeter不便之处在于填了params就不能写Body Data,遇见一些接口两个都需要时就十分尴尬,这里的思路上把放在params的参数写成url放入环境变量里,这里会涉及到url里特殊字符的编码处理

只带params的请求

  1. 在params的填写处,jmeter提供了自带的参数编码,故这里不做特殊处理,建立用户自定义变量,将要传入的params写入变量
    Jmeter字符串MD5和文件MD5生成签名_第5张图片
    Jmeter字符串MD5和文件MD5生成签名_第6张图片
  2. 创建http请求,并添加前置beanshell处理器,直接来看里面的代码
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即可。
Jmeter字符串MD5和文件MD5生成签名_第7张图片
注意在发送http请求时加上请求头部的内容类型设置,不然网站后台会无法解析发送数据
Jmeter字符串MD5和文件MD5生成签名_第8张图片
Jmeter字符串MD5和文件MD5生成签名_第9张图片

带body和parameters的请求

由于jmeter的特殊情况,这里只能把添加parameters后的url先写出来,再对一些特殊字符进行编码转义,最后传到url变量里,在请求处直接调用url,而不是像get请求一样一个个在parameters处填写参数,让jmeter自己拼接参数和编码。

  1. 同样也是在请求里添加预置beanshell处理器,在处理器写入相应的代码
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的函数,只需要调用就行了
Jmeter字符串MD5和文件MD5生成签名_第10张图片
将生成的${__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();

你可能感兴趣的:(学习笔记)