前言
兄弟们,不知不觉这个【精通JMeter接口测试】系列已经走到了最后一篇。
从最初的环境搭建、断言、参数化,到后来的关联、控制器、我们一步步把 JMeter 从"能用"玩到了"精通"。
而今天这最后一块拼图,叫做 BeanShell + 接口签名。
BeanShell 是 JMeter 中的"脚本瑞士军刀",能让你在测试过程中动态处理数据、读写文件、调用 Java 代码------几乎所有内置组件搞不定的逻辑,它都能兜底。
而接口签名(如 MD5、RSA、HmacSHA256)是现代 API 安全防篡改、防重放的核心手段。不会签名,很多企业级接口你连调都调不通。
本篇会把这两者彻底讲透:从 BeanShell 基础语法、常用内置变量,到如何用 BeanShell 前置处理器动态生成签名参数,再到封装成可复用的脚本。
学完这一篇,你手里的 JMeter 就真的"精通"了。
目录
- 前言
- 一、Jmeter接口测试之BeanShell
-
- 1、什么是BeanShell
- 2、BeanShell常用的内置变量和语法规则
-
- [2.1 内置对象](#2.1 内置对象)
- [2.2 BeanShell 核心语法](#2.2 BeanShell 核心语法)
-
- [2.2.1. 日志打印(调试用)](#2.2.1. 日志打印(调试用))
- [2.2.2. 局部变量操作(vars:单个线程内使用)](#2.2.2. 局部变量操作(vars:单个线程内使用))
- [2.2.3. 全局变量操作(props:跨线程/多线程使用)](#2.2.3. 全局变量操作(props:跨线程/多线程使用))
- (1)props.put():设置全局变量
- (2)props.get():获取全局变量
- [2.2.4. 获取上一个请求的响应信息(prev)](#2.2.4. 获取上一个请求的响应信息(prev))
- [3、BeanShell 调用外部代码](#3、BeanShell 调用外部代码)
-
- [3.1. BeanShell 直接调用 Java 文件](#3.1. BeanShell 直接调用 Java 文件)
- [3.2. BeanShell 调用 打包好的 Jar 包](#3.2. BeanShell 调用 打包好的 Jar 包)
- [3.3. BeanShell 调用 Python 代码(无参数)](#3.3. BeanShell 调用 Python 代码(无参数))
- [3.4. BeanShell 调用 Python 代码(传1个参数)](#3.4. BeanShell 调用 Python 代码(传1个参数))
- [3.5. BeanShell 调用 Python 代码(传2个参数)](#3.5. BeanShell 调用 Python 代码(传2个参数))
- [3.6. BeanShell 调用 Python 实现 RSA 加密](#3.6. BeanShell 调用 Python 实现 RSA 加密)
-
- [3.6.1 实战演示](#3.6.1 实战演示)
- [3.7. 解释为什么我们在这个里面它不直接执行Python代码,而是用Java去执行Python代码呢?](#3.7. 解释为什么我们在这个里面它不直接执行Python代码,而是用Java去执行Python代码呢?)
- 二、Jmeter接口测试之接口签名
-
- 1、为啥要有接口签名?
- 2、接口签名到底是什么?
- 3、接口签名三大核心作用
- 4、一步步拆解签名规则
-
- [4.1. 收集所有接口参数(查询字符串或Body参数等等),按键名 ASCII 升序排序](#4.1. 收集所有接口参数(查询字符串或Body参数等等),按键名 ASCII 升序排序)
- [4.2. 参数键值用=连,多个参数用&连](#4.2. 参数键值用=连,多个参数用&连)
- [4.3. 头部拼平台给的 appid、appsecret](#4.3. 头部拼平台给的 appid、appsecret)
- [4.4. 尾部拼随机流水号 nonce](#4.4. 尾部拼随机流水号 nonce)
- [4.5. 再尾部拼时间戳 timestamp](#4.5. 再尾部拼时间戳 timestamp)
- [4.6. 整串做 MD5 加密,再转大写](#4.6. 整串做 MD5 加密,再转大写)
- [4.7. 把 Sign 放到请求头里一起发给后端](#4.7. 把 Sign 放到请求头里一起发给后端)
- 5、实战
-
- 5.1、先把你这个接口的所有参数全扒出来
-
- 1). URL 查询参数(路径里的). URL 查询参数(路径里的))
- 2). Body 请求体参数(表单里的). Body 请求体参数(表单里的))
- 5.2、这个接口的「通用签名规则」是什么?
- 5.3、要知道为什么要按这个规则搞?
- 5.4、我们一起走一遍完整流程
-
- 第1步:收集所有非动态参数(URL+Body)
- 第2步:按key的ASCII码升序排序
- [第3步:拼成 `key=value&` 格式](#第3步:拼成
key=value&格式) - [第4步:头部拼接 appid 和 appsecret](#第4步:头部拼接 appid 和 appsecret)
- [第5步:尾部拼接 nonce(随机流水号)](#第5步:尾部拼接 nonce(随机流水号))
- [第6步:尾部拼接 timestamp(时间戳)](#第6步:尾部拼接 timestamp(时间戳))
-
- [补充BeanShell 获取时间戳(Java原生代码)](#补充BeanShell 获取时间戳(Java原生代码))
- [第7步:对整串做 MD5 加密,再转大写](#第7步:对整串做 MD5 加密,再转大写)
- 第8步:把sign放到请求头里带给我们的后端
- [5.5 接口签名BeanShell脚本(Java语言版本)](#5.5 接口签名BeanShell脚本(Java语言版本))
- [5.6 最后一步:请求头配置(必须做)](#5.6 最后一步:请求头配置(必须做))
- [5.7 疑问1🤔`我们有接口签名,那是不是就不用写参数了?`](#5.7 疑问1🤔
我们有接口签名,那是不是就不用写参数了?) - [5.8 疑问2🤔`那接口签名BeanShell脚本我们是写在哪里呢?是写在后置处理器还是预处理器?`](#5.8 疑问2🤔
那接口签名BeanShell脚本我们是写在哪里呢?是写在后置处理器还是预处理器?) - [5.9 总结](#5.9 总结)
- 三、写在最后
一、Jmeter接口测试之BeanShell
1、什么是BeanShell
BeanShell是jmeter的扩展组件,因为jmeter它有好多东西是不支持的,比如某些加密算法函数,它不支持。此时我们只有通过BeanShell来扩展。
BeanShell他可以写Java代码和它自带的BeanShell代码。
BeanShell有很多种:
- 前置处处理器有BeanShell
- 定时器有BeanShell
- 采样器有BeanShell
- 后置处理器有BeanShell
- 断言有BeanShell
- 监听器有BeanShell
注意:
BeanShell他们都是一样的。只是你在什么时间去执行,举个例子:如果你在请求之前执行,那么你 就是前置处理器的BeanShell。
2、BeanShell常用的内置变量和语法规则
2.1 内置对象
vars:操作局部变量(单个线程组内使用)props:操作全局变量(跨线程组/多线程使用)prev:获取上一个请求的响应结果log:打印日志(调试必备)
2.2 BeanShell 核心语法
没一句代码都要以分号结尾
2.2.1. 日志打印(调试用)
java
// 功能:在JMeter日志中输出指定文字,用于调试代码
log.info("我可以用来调试。。。");


2.2.2. 局部变量操作(vars:单个线程内使用)
(1)vars.put():设置局部变量/操作局部变量
不能够跨线程组,只能在一个线程组里面使用的变量叫做局部变量。
java
// 功能:设置局部变量,变量名age,变量值18(只能当前线程组使用)
vars.put("age","18");
(2)vars.get():获取局部变量
如图所示,比如我创建一个配置元件,用户定义的变量。我在里面加一个变量叫做李四。


获取用户定义的变量
java
// 1. 获取用户自定义的变量,赋值给字符串变量n
String n = vars.get("name");
// 打印变量值
log.info(n);

获取正则表达式提取的变量
java
// 2. 获取正则表达式提取器提取的变量(token)
String token = vars.get("csrf_token");
log.info(token);


获取其他BeanShell设置的变量
比如我有一个BeanShell后置处理程序
我在里面定义一个age,值为18

java
// 3. 获取其他BeanShell设置的变量
String a = vars.get("age");
log.info(a);

2.2.3. 全局变量操作(props:跨线程/多线程使用)
(1)props.put():设置全局变量
java
// 功能:设置全局变量,变量名www,变量值zhongge(所有线程组都能调用)
props.put("www","zhongge");
举个例子,我在线程组5中设置一个全局变量,怎么设置,我们在这个后置BeanShell中,添加一个name:"zhongge"

(2)props.get():获取全局变量
在线程组6里面添加一个前置BeanShell处理器。

java
// 获取全局变量www的值
String w = props.get("www");
// 打印变量值
log.info(w);

2.2.4. 获取上一个请求的响应信息(prev)
那获取请求的响应信息这个是容易掉坑的。
首先,我们先来掉坑:
首先我们看登录接口如下图👇

于是我想获取他的响应状态码, Ok,咱老铁就把它写在前置处理器中,于是就右键接口,添加一个前置的BeanShell处理器

我们在里面写下如下代码:
获取上一个接口请求的响应状态码
java
// 获取上一个接口请求的响应状态码(如:200、404、500)
log.info(prev.getResponseCode());
结果:报错了


请求是通过的呀,为什么会报错获取不到响应状态码呢?原因其实很简单。
你为什么要用前置处理器呢?前置处理器的话,它是在接口执行之前。你接口都没有执行,你哪儿来获取他的状态吗?是吧?你只有等接口执行完之后,他才会给你返回响应数据以及状态码等。
因此换句话说,我们这里不能用前置BeanShell,而是用后置BeanShell。
Ok,此时我们改为后置BeanShell

执行结果:使用后置处理器之后就正确了,👇

获取上一个接口请求的完整响应数据(转为字符串)
java
// 获取上一个接口请求的完整响应数据(转为字符串)
log.info(prev.getResponseDataAsString());
这个也是一样的,注意一点就是我们得使用后置处理器。

3、BeanShell 调用外部代码
3.1. BeanShell 直接调用 Java 文件
Ok,我们先写个Java代码

然后把这个Java文件呢,放到我们的本地目录

当然这里不是说让老铁们去学习怎么写Java。我们到时候这些代码都是人家写好的,我们只需要会用就行。
我们先添加一个BeanShell取样器来测试一下:

测试代码如下:
java
// 1. 引入本地Java文件(填写文件绝对路径)
source("E://ants//Test.java");
// 2. 创建Test类的对象,调用类中的add方法,传入参数3和5
int sum = new Test().add(3,5);
// 3. 将计算结果转为字符串并打印日志
log.info(sum.toString());
结果如下:

3.2. BeanShell 调用 打包好的 Jar 包

怎么去执行这个jar包呢?跟我一起做。
首先把jar包导入进来👇



代码:
java
// 1. 有包就导入jar包中的test类(包名+类名)
//没有就算了
// 2. 创建对象调用方法,计算10+20
int sum = new Test().add(10,20);
// 3. 打印结果
log.info("相加的结果:" + sum.toString());
结果

3.3. BeanShell 调用 Python 代码(无参数)
1)首先,新建一个py文件,用于测试
py
# E:/ants/test.py
def hello():
return "我是Python脚本,调用成功啦!"
if __name__ == "__main__":
# 给JMeter返回内容
print(hello())
print("200 调用正常")

2)其次就是 我们最后是需要执行你这个py代码的,所以此处我们得去找一下我们的python解析器


Ok,现在就加一个取样器,然后再执行下面的代码。

java
// 导入Java流操作包,用于读取命令行执行结果
import java.io.BufferedReader;
import java.io.InputStreamReader;
// 拼接cmd命令:执行python脚本(cmd /c 执行完自动关闭窗口)
String command = "cmd /c D:/PythonProject/python.exe E:/ants/test.py";
// 获取Java运行时对象
Runtime rt = Runtime.getRuntime();
// 执行cmd命令,启动进程
Process pr = rt.exec(command);
// 等待Python脚本执行完成
pr.waitFor();
// 读取进程的输出流(获取Python脚本返回结果)
BufferedReader b = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String line = "";
// 字符串拼接器,存储完整返回结果
StringBuilder response = new StringBuilder();
// 循环读取每一行输出
while ((line = b.readLine()) != null) {
response.append(line);
}
// 转为字符串
String response_data = response.toString();
// 关闭流
b.close();
// 打印Python脚本执行结果
log.info(response_data);
注意:需提前安装
pip install argparse

3.4. BeanShell 调用 Python 代码(传1个参数)
py
# 导入命令行参数工具,用来接收 -t 传过来的参数
import argparse
# 创建参数解析器对象
parser = argparse.ArgumentParser()
# 定义要接收的参数:-t 是参数名,必须传字符串,不能为空
parser.add_argument('-t', type=str, required=True, help='接收JMeter传过来的参数')
# 解析命令行传入的所有参数
args = parser.parse_args()
# 把传入的参数取出来,赋值给变量
param = args.t
# 打印内容,会被JMeter读取到日志里
print(f"JMeter传过来的参数是:{param}")
print("参数接收成功,脚本执行完毕")

执行代码:
java
// 导入Java流读取工具,用来接收Python脚本返回的数据
import java.io.BufferedReader;
import java.io.InputStreamReader;
// 拼接执行命令:调用你电脑D盘的Python,执行E盘ants下的test_args.py,并且传参数 -t admin
String command = "cmd /c D:/PythonProject/python.exe E:/ants/test_args.py -t admin";
// 获取系统运行时对象,用来执行cmd命令
Runtime rt = Runtime.getRuntime();
// 执行上面拼接好的命令,启动Python脚本进程
Process pr = rt.exec(command);
// 暂停等待,等Python脚本全部执行完再往下走
pr.waitFor();
// 创建读取器,读取Python脚本打印出来的内容
BufferedReader b = new BufferedReader(new InputStreamReader(pr.getInputStream()));
// 定义临时变量,存每一行读取到的数据
String line = "";
// 字符串拼接工具,把Python所有返回内容拼到一起
StringBuilder response = new StringBuilder();
// 循环读取Python返回的每一行内容
while ((line = b.readLine()) != null) {
response.append(line);
}
// 把拼接好的内容转成普通字符串
String response_data = response.toString();
// 关闭读取流,释放资源
b.close();
// 在JMeter日志中打印Python返回的结果
log.info("Python 带参运行返回:" + response_data);
注意:需提前安装
pip install argparse
结果:

3.5. BeanShell 调用 Python 代码(传2个参数)
创建py代码
py
# 导入命令行参数解析库,用来接收JMeter传过来的参数
import argparse
# 创建参数解析器
parser = argparse.ArgumentParser()
# 接收第一个参数:-t 账号
parser.add_argument("-t", required=True, help="传入账号")
# 接收第二个参数:-p 密码
parser.add_argument("-p", required=True, help="传入密码")
# 解析所有传入的参数
args = parser.parse_args()
# 获取参数1:账号
username = args.t
# 获取参数2:密码
password = args.p
# 打印内容,返回给JMeter
print("接收参数成功!")
print(f"账号:{username}")
print(f"密码:{password}")
print("脚本执行完毕!")

java
// 导入读取Python返回结果需要的工具包
import java.io.BufferedReader;
import java.io.InputStreamReader;
// 拼接CMD命令
// cmd /c = 执行命令后关闭窗口
// D:/PythonProject/python.exe = 你电脑的Python路径
// E:/ants/test_two_args.py = 你的Python脚本
// -t admin -p 123 = 传给Python的两个参数:账号admin、密码123
String command = "cmd /c D:/PythonProject/python.exe E:/ants/test_two_args.py -t admin -p 123";
// 获取系统运行对象,用来执行CMD命令
Runtime rt = Runtime.getRuntime();
// 执行命令,启动Python脚本
Process pr = rt.exec(command);
// 等待Python脚本完全运行结束,再继续执行后面代码
pr.waitFor();
// 创建读取流,读取Python打印出来的内容
BufferedReader b = new BufferedReader(new InputStreamReader(pr.getInputStream()));
// 定义变量,接收每一行内容
String line = "";
// 字符串拼接器,把所有返回结果拼在一起
StringBuilder response = new StringBuilder();
// 循环读取Python返回的每一行数据
while ((line = b.readLine()) != null) {
response.append(line);
}
// 把最终结果转成字符串
String response_data = response.toString();
// 关闭流
b.close();
// 在JMeter日志打印Python返回的结果
log.info("Python双参数返回:" + response_data);
结果:

3.6. BeanShell 调用 Python 实现 RSA 加密
RSA规则:首先会生成公钥和私钥,然后用公钥来加密,用私钥来解密。
OK,咱先用Python代码写出这个加密的算法。这些算法的话复制粘贴就行了,然后我们再用Java去操作执行我们这个Python代码。这里不要懵,至于我们跑Python代码为什么要用Java去操作?我们3.7会解释。
Python RSA 加密代码:
py
# 导入真正的RSA加密算法库
import rsa
# 导入传参工具
import argparse
# 解析传入的账号密码参数
parser = argparse.ArgumentParser()
parser.add_argument("-t", required=True)
parser.add_argument("-p", required=True)
args = parser.parse_args()
user = args.t
pwd = args.p
# ========== 这里才是真正的RSA加密算法开始 ==========
# 1. 生成公钥、私钥
pub_key, priv_key = rsa.newkeys(512)
# 2. 公钥加密账号、转16进制字符串方便JMeter接收
enc_user = rsa.encrypt(user.encode("utf-8"), pub_key).hex()
# 3. 公钥加密密码
enc_pwd = rsa.encrypt(pwd.encode("utf-8"), pub_key).hex()
# ========== RSA加密算法结束 ==========
# 用###分隔返回给JMeter
print(f"{enc_user}###{enc_pwd}")

接下来就用我们的BeanShell去执行:
java
// 导入用于读取Python输出内容的工具类
import java.io.BufferedReader;
// 导入将字节流转换为字符流的工具类
import java.io.InputStreamReader;
// 1. 拼接CMD命令:调用Python程序执行加密脚本
// cmd /c 执行完命令后自动关闭CMD窗口
// D:/PythonProject/python.exe 你电脑上的Python解释器路径
// E:/ants/rsa_encrypt.py 你的RSA加密Python脚本路径
// -t admin 向脚本传递参数:用户名 admin
// -p 123 向脚本传递参数:密码 123
String command = "cmd /c D:/PythonProject/python.exe E:/ants/rsa_encrypt.py -t admin -p 123";
// 2. 获取Java运行时环境对象,用于执行操作系统命令
Runtime rt = Runtime.getRuntime();
// 3. 执行拼接好的CMD命令,启动Python进程
Process pr = rt.exec(command);
// 4. 等待Python脚本执行完毕,再执行后面的代码(防止还没跑完就读取结果)
pr.waitFor();
// 5. 获取Python进程的输出流,并包装成缓冲读取器(方便读取打印内容)
BufferedReader b = new BufferedReader(new InputStreamReader(pr.getInputStream()));
// 定义临时字符串变量,存储每一行读取到的内容
String line = "";
// 定义字符串构建器,拼接Python返回的所有加密结果
StringBuilder response = new StringBuilder();
// 6. 循环读取Python输出的每一行内容,直到读完为止
while ((line = b.readLine()) != null) {
// 将读到的内容追加到结果字符串中
response.append(line);
}
// 7. 将拼接完成的最终结果转为字符串
String response_data = response.toString();
// 8. 关闭读取流,释放系统资源
b.close();
// 9. 按照分隔符 ### 拆分字符串:[加密账号, 加密密码]
String[] msg = response_data.split("###");
// 打印输出加密后的账号
log.info("账号:" + msg[0]);
// 打印输出加密后的密码
log.info("密码:" + msg[1]);
// 10. 将加密结果存入JMeter变量,方便接口调用
// 后续在请求参数中直接使用 ${usn} 和 ${psw}
vars.put("usn", msg[0]);
vars.put("psw", msg[1]);
注意:需安装
pip install rsa如果你的结果没运行出来,那就是你这个东西没安装。

3.6.1 实战演示
接口文档
简要描述:RSA加密接口
请求URL:
http://116.62.63.211:5000/rsalogin请求方式:
POST请求参数:
参数名 必选 类型 说明 备注 username 是 string 用户名:admin 用户名必须RSA加密处理 password 是 string 密码:123 密码必须RSA加密处理 RSA加密公钥
-------BEGIN RSA PUBLIC KEY------- MIGJAoGBALO7UPE26anTGHND2Q54zYYPusDx+tbO1Yia7zoxpZediw+Baea7aFZC J+ZvWd5ZBTopuWvb8hNkY24eBHcXN0pU32WjsH9REp1kXhxbndnw+u3diaoUFqVc 66xl+LXEo1Y9oDWfkGCir2JnN0aieUiPlHDLhmc+LII/ZDspITKDAgMBAAE= -------END RSA PUBLIC KEY-------RSA加密私钥
-------BEGIN RSA PRIVATE KEY------- MIICYAIBAAKBgQCzu1DxNump0xhzQ9kOeM2GD7rA8frWztWImu86MaWXnYsPgWnm u2hWQifmb1neWQU6Kblr2/ITZGNuHgR3FzdKVN9lo7B/URKdZF4cW53Z8Prt3Ymq FBalXOusZfi1xKNWPaA1n5Bgoq9iZzdGonlIj5Rwy4ZnPiyCP2Q7KSEygwIDAQAB AoGAP8Rxz2NAO/SddCfaVvwdTzc9dz9jU9tGf5UY03jR250VvYaY4DgVqKUIKwXO yMikxiIPm2kJ1j+D14nmmfYdn0FGltuwbubNqFMgZC33dx1fyPFtC2ChBRg2VpY9 K/WM0jvPpA2j9SUtEpLSK2dPnpUYOerF0fWljTA6ydzun0ECRQDPu9bUUYylVfCH AHZ+5TqtiI5O0xqYs2ZkgkkvtBYxd7a7A3zHclKbiMHs6x7WmfReTaIV/P896msQ w4Newr/KYYH5qQI9AN195O2DYcGhIqS8hPv2uy5cXQoJ4chp7NnkABL2dJ0BkTD7 K1G/uafdb/rCqTtp5TSQcM0hqWLuYfleSwJEQLnCYj1WmV2BqE7YnQlHkzJtRNo5 +0JKsR53N0nhcGBgqzyW8H4KhzPBWNSZszwdDBUcX8WIsf4MhhilIi2EHMbs/KEC PCAPLumqEKZEOo15tA38Yo+NUvI3B/VIT74iIFieT7bCGU/rqxOEGs1PYfv73R76 bvCyz4EBlFD7jDbaDQJFAJjl9poUrOHwh0ybNC13lCFhH4oWILufUoaGfa25jFuE MYS7Ri5vwf8tcbbdZQbILa2LVhOxgmqCCUoav3pPe8YdEYEP -------END RSA PRIVATE KEY-------返回示例
bash{"error_code":0,"message":"RSA加密登陆成功!"}返回参数说明:
参数名 类型 说明 message string success表示登陆成功
1)好的,此时python代码中的秘钥得修改一下:
py
# 1. 导入命令行参数接收库(用来接收JMeter传的账号、密码)
import argparse
# 2. 导入Base64编码库(把加密后的二进制转成字符串,接口才能识别)
import base64
# 3. 导入RSA加密库(核心加密工具)
import rsa
# 定义RSA加密函数(给JMeter调用)
def rsa_jiami():
# ===================== 【第一步:接收JMeter传过来的参数】 =====================
# 创建参数解析器
parse = argparse.ArgumentParser()
# 定义接收参数:-t 代表用户名(必须传)
parse.add_argument("-t", required=True)
# 定义接收参数:-p 代表密码(必须传)
parse.add_argument("-p", required=True)
# 解析传进来的参数
args = parse.parse_args()
# 从参数中取出 用户名(来自 -t)
username = args.t
# 从参数中取出 密码(来自 -p)
password = args.p
# ===================== 【第二步:加载接口给的固定公钥】 =====================
# 接口文档提供的固定RSA公钥(前后端约定好,不能改)
public_key_str = """-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBALO7UPE26anTGHND2Q54zYYPusDx+tbO1Yia7zoxpZediw+Baea7aFZC
J+ZvWd5ZBTopuWvb8hNkY24eBHcXN0pU32WjsH9REp1kXhxbndnw+u3diaoUFqVc
66xl+LXEo1Y9oDWfkGCir2JnN0aieUiPlHDLhmc+LII/ZDspITKDAgMBAAE=
-----END RSA PUBLIC KEY-----"""
# 把字符串公钥 变成 Python 能识别的公钥对象
pubkey = rsa.PublicKey.load_pkcs1(public_key_str.encode())
# ===================== 【第三步:固定加密!不再随机!】 =====================
# 核心修复:接口需要 "无填充 / 固定结果"
user_enc = rsa.encrypt(username.encode('utf-8'), pubkey)
pwd_enc = rsa.encrypt(password.encode('utf-8'), pubkey)
# 把加密后的二进制 转成 Base64字符串(接口要求的格式)
user_base64 = base64.b64encode(user_enc).decode("utf-8")
pwd_base64 = base64.b64encode(pwd_enc).decode("utf-8")
# ===================== 【第四步:输出给JMeter】 =====================
# 输出格式:加密账号###加密密码,方便JMeter切割使用
print(f"{user_base64}###{pwd_base64}")
# 程序入口:运行加密函数
if __name__ == '__main__':
rsa_jiami()
2)我们就加这个接口如下,👇

3)右键接口添加一个前置处理器,用于得出加密之后的账号,必须是前置处理器,因为我们要加密之后再请求

4)OK,那么此时我们就使用BeanShell脚本对admin和123进行加密,结果如下:

5)将结果加密后的账号复制进去
注意,因为我们存值了,所以直接使用${}取值

注意事项

6)结果:正确

3.7. 解释为什么我们在这个里面它不直接执行Python代码,而是用Java去执行Python代码呢?
你想过没有?我们上述是要去跑Python代码的,但是这个Python它的执行过程是用Java来操作的, 这是为什么呢?
原因如下:
核心一句话:
因为 JMeter 本身只支持 Java,不支持直接跑 Python!
- 先搞清楚:JMeter 是什么语言写的?
JMeter 是纯 Java 开发的工具
它的内核、运行引擎、BeanShell 全都跑在 Java 环境里。
它根本不认识 Python 代码!
你直接在 JMeter 里写 Python,它完全识别不了,不知道是什么语法。- 那我们明明用 JMeter,为什么还要用 Python?
像 RSA 加密、AES 加密、接口签名、复杂算法、调用第三方接口这些功能;
Python 写起来超级简单、代码少、上手快。
换成纯 Java 写,代码又多又繁琐,写起来很费劲。- 所以才有了这种搭配逻辑:
JMeter(Java) → 调用 Python → 拿到处理好的结果
打个比方:
你不会说英语,你朋友会英语;
你让朋友帮你跟老外沟通,最后把结果转告你。
JMeter 相当于你,Python 相当于会英语的朋友,BeanShell 就是传话调用的中间人。- 为什么 JMeter 不能直接运行 Python?
JMeter 没有内置 Python 解释器和运行环境;
JMeter 只识别 Java 的 class、jar、BeanShell 语法;
Python 是独立脚本语言,不能直接嵌入到 JMeter 内核里运行。
只能靠 Java(BeanShell)调用系统 CMD 命令,拉起 Python 脚本执行,再拿回结果。- 执行流程逻辑
JMeter 界面
往下走到 BeanShell(Java 代码)
再调用 cmd / python.exe 程序
运行 Python 脚本做加密、算法处理
把处理完的结果回传给 JMeter
JMeter 拿到结果直接用于接口请求- 最通俗总结
JMeter 只会 Java 语法,不认 Python;
但 Python 写加密、算法更简单省事;
所以只能用 Java 当中间人,调用 Python 帮忙干活。- 记住这三点就够用
JMeter 底层是 Java 开发的;
JMeter 原生不能直接运行 Python 脚本;
只能通过 BeanShell 写 Java 代码,间接调用 Python 做加密、签名、复杂算法。
一句话终极总结:不是不想直接用 Python,是 JMeter 原生根本不支持,只能用 Java 中转间接调用。
二、Jmeter接口测试之接口签名
1、为啥要有接口签名?
现在前后端、APP 和服务器都是接口互相传数据。
如果不加签名,坏人就能搞三件坏事:
- 偷偷看到你的接口账号、密钥,直接盗用;
- 中途篡改你传的参数,比如把商品价格改成1块、把别人订单改成自己的;
- 拿着你之前发过的接口请求,重复疯狂提交,恶意刷单、重复下单。
所以公司就搞了一套自定义加密规则 ,不是普通 RSA/MD5 那种单纯加密,
叫接口签名 Sign,专门防泄露、防篡改、防重复提交。
2、接口签名到底是什么?
接口签名:Sign就是一个特殊的自定义加密。
Sign 就是公司自己定的一套专属加密规则
不是固定算法,每家公司规则都稍微不一样,但套路大同小异。
把接口所有
参数按规矩拼接、加盐、加随机号、加时间,最后 MD5 加密转大写,生成一串唯一验证码 ,这个验证码就是 Sign。接口签名实际针对的是
接口参数
后端给你定了一套「签名规则」,你必须按规则算出一个 sign,再把它放到请求头里一起发过去。 后端收到请求,会按一模一样的规则,自己再算一遍 sign。 两个 sign 一模一样:放行,接口请求成功 不一样:直接判定你参数被篡改、请求非法,接口直接报错,根本不会处理你的请求
3、接口签名三大核心作用
- 防止接口密钥泄露
普通人看不懂拼接规则和加密串,坏人拿不到真实账号密钥。- 防止接口参数被篡改
参数必须按规则排序拼接,改任何一个字,生成的 Sign 立马变,后端一比对就知道被改了,直接拒绝请求。- 防止接口被重复提交
加随机流水号 nonce + 时间戳 timestamp
限定十几秒内有效,过期作废,坏人拿旧请求重复发也没用。
4、一步步拆解签名规则
4.1. 收集所有接口参数(查询字符串或Body参数等等),按键名 ASCII 升序排序
比如接口传了 c=3,a=2,b=1
乱序不行,必须按字母顺序排成:a、b、c
固定顺序,不然后端和前端拼的字符串不一样,签名对不上。
4.2. 参数键值用=连,多个参数用&连
排好序后拼成:a=2&b=1&c=3
4.3. 头部拼平台给的 appid、appsecret
appid、appsecret 相当于项目的账号密码 ,只有正规合作方才有。
拼完:appid=admin&appsecret=123&a=2&b=1&c=3
4.4. 尾部拼随机流水号 nonce
一串随机十位以上字符串,作用:防重复提交
拼完:appid=admin&appsecret=123&a=2&b=1&c=3&nonce=1234567890
4.5. 再尾部拼时间戳 timestamp
当前时间戳,一般限定10分钟内有效 ,超时就失效,防止旧请求被拿去复用。
拼完一长串完整原始字符串。
4.6. 整串做 MD5 加密,再转大写
加密完生成一长串字符,就是 Sign 签名值。
4.7. 把 Sign 放到请求头里一起发给后端
后端拿同样规则再拼一遍、加密一次,对比你传过来的 Sign:
一样 = 合法请求,正常通过;
不一样 = 被篡改、过期、非法请求,直接拦截。
5、实战
如图所示,咱们要请求这个接口,那么这个接口里面有很多参数,我们按照上述步骤一步一步做。

5.1、先把你这个接口的所有参数全扒出来
1). URL 查询参数(路径里的)
http://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun
这里的参数是:
m=uc=logina=dorun
2). Body 请求体参数(表单里的)
username=lisipassword=123456csrf_token=${token}(这个是动态获取的,先不算进排序)backurl=http://47.107.116.139/phpwind/invite=(空值)
5.2、这个接口的「通用签名规则」是什么?
接口签名 Sign,就是后端定的一套固定拼接+加密流程 ,防止参数被篡改、请求被盗用/重放。
你这个接口的签名规则,就是按下面7步走:
- 收集所有非动态参数:包括URL查询参数 + Body表单参数(csrf_token这种动态值除外)
- 把所有参数的
key,按ASCII码升序排列 - 把每个参数拼成
key=value格式,多个参数用&连接 - 头部拼接平台给的
appid和appsecret(项目的"账号密码") - 尾部拼接
nonce(随机流水号,防重复提交) - 尾部拼接
timestamp(时间戳,限定10分钟内有效) - 对整串字符串做 MD5加密 ,再转大写,得到
sign,放到请求头里
5.3、要知道为什么要按这个规则搞?
- 按key排序:前后端拼接顺序必须一致,不然签名永远对不上
- 固定拼接格式:
key=value&,统一格式,避免歧义- 加appid/appsecret:证明你是合法调用方,不是随便谁都能发请求
- 加nonce:防止别人拿你发过的请求,重复提交刷单
- 加timestamp:防止旧请求被别人拿去复用,过了时间就失效
- MD5加密转大写:把明文变成不可逆的密文,别人看不懂也改不了参数
5.4、我们一起走一遍完整流程
第1步:收集所有非动态参数(URL+Body)
先把所有要参与签名的参数列出来:
bash
URL参数:m=u, c=login, a=dorun
Body参数:username=lisi, password=123456, backurl=http://47.107.116.139/phpwind/, invite=
(csrf_token是动态的,规则里说"动态参数除外",所以不参与排序和拼接)
第2步:按key的ASCII码升序排序
按字母顺序 a → b → c → i → m → p → u 排序:
bash
a=dorun
backurl=http://47.107.116.139/phpwind/
c=login
invite=
m=u
password=123456
username=lisi
第3步:拼成 key=value& 格式
bash
a=dorun&backurl=http://47.107.116.139/phpwind/&c=login&invite=&m=u&password=123456&username=lisi
第4步:头部拼接 appid 和 appsecret
假设项目密钥是:
appid=adminappsecret=123
拼接后:
bash
appid=admin&appsecret=123&a=dorun&backurl=http://47.107.116.139/phpwind/&c=login&invite=&m=u&password=123456&username=lisi
第5步:尾部拼接 nonce(随机流水号)
假设随机号是 nonce=1234567890:
bash
appid=admin&appsecret=123&a=dorun&backurl=http://47.107.116.139/phpwind/&c=login&invite=&m=u&password=123456&username=lisi&nonce=1234567890
第6步:尾部拼接 timestamp(时间戳)
假设当前时间戳是 timestamp=1746000000:
bash
appid=admin&appsecret=123&a=dorun&backurl=http://47.107.116.139/phpwind/&c=login&invite=&m=u&password=123456&username=lisi&nonce=1234567890×tamp=1746000000
此处我们时间戳怎么生成的?
补充BeanShell 获取时间戳(Java原生代码)
java
// 导入Java时间包
import java.time.Instant;
// 获取当前时间戳(毫秒级)
long timestamp = Instant.now().toEpochMilli();
// 打印时间戳
log.info("时间戳:"+timestamp);

第7步:对整串做 MD5 加密,再转大写
MD5加密 在我们的jmeter中 MD5函数如下所示:注意jmeter5.6.3是digest函数

加密后得到:
bash
2b04d7d88195dbf9af934ffbcb800493

再将他变为大写:
bash
2B04D7D88195DBF9AF934FFBCB800493
这串大写字母数字,就是你最终的接口签名 sign。
第8步:把sign放到请求头里带给我们的后端
我们请求头中是键值对的方式。
比如我们请求头里加一行:
java
Authorization: Sgin 2B04D7D88195DBF9AF934FFBCB800493
为什么是Authorization: Sgin 2B04D7D88195DBF9AF934FFBCB800493 不是Authorization Sgin : 2B04D7D88195DBF9AF934FFBCB800493一个例子看懂:
那么后续我们后端会按一模一样的规则,再拼一遍字符串、算一次MD5,和你传的sign对比,一致就放行,不一致就拦截。
接下来我们用这个接口的数据,写一个完整的 JMeter BeanShell 脚本,自动完成参数排序、拼接、生成签名👇
5.5 接口签名BeanShell脚本(Java语言版本)
注意:这里我们必须写在前置处理器中(后面会有解释)

java
// ====================== 【1】导入需要的包 ======================
import org.apache.commons.codec.digest.DigestUtils;
import java.util.Map;
import java.util.TreeMap;
import java.util.Iterator;
// ====================== 【2】收集所有参数(修复版!) ======================
Map params = new TreeMap(); // 去掉 <>,BeanShell 兼容
// 放入所有参数(自动ASCII排序)
params.put("a", "dorun");
params.put("c", "login");
params.put("m", "u");
params.put("username", "lisi");
params.put("password", "123456");
params.put("backurl", "http://47.107.116.139/phpwind/");
params.put("invite", "");
// ====================== 【3】拼接 key=value&key=value ======================
StringBuilder sb = new StringBuilder();
Iterator it = params.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
sb.append(key).append("=").append(value).append("&");
}
String paramStr = sb.substring(0, sb.length() - 1);
// ====================== 【4】拼接 appid + appsecret ======================
String appid = "admin";
String appsecret = "123";
String signStr = "appid=" + appid + "&appsecret=" + appsecret + "&" + paramStr;
// ====================== 【5】拼接 nonce ======================
String nonce = "1234567890";
signStr = signStr + "&nonce=" + nonce;
// ====================== 【6】拼接 时间戳 ======================
long timestamp = System.currentTimeMillis() / 1000;
signStr = signStr + "×tamp=" + timestamp;
// ====================== 【7】MD5 + 大写 ======================
String sign = DigestUtils.md5Hex(signStr).toUpperCase();
// ====================== 【8】存入变量 ======================
vars.put("sign", sign);
vars.put("timestamp", String.valueOf(timestamp));
vars.put("nonce", nonce);
// ====================== 打印日志 ======================
log.info("最终签名字符串:" + signStr);
log.info("生成签名:" + sign);
测试结果:

5.6 最后一步:请求头配置(必须做)
添加 HTTP信息头管理器 :

| 名称 | 值 |
|---|---|
| Authorization | Sign ${sign} |

🚀 Ok,到这我们就演示结束了。那需要补充一点就是,由于我没有找到对应的接口,我们上述的演示接口只是带大家去看一下这个接口签名要走的流程,最终去运行的话,因为人家的,呃,需求里面就不需要这个东西,所以我就运行不了,后续我找到对应的例子,会给老铁们更新。
但是流程都是上述的例子,我们这里只是差了一步运行而已😁
5.7 疑问1🤔我们有接口签名,那是不是就不用写参数了?

答: 先给结论,
❌ 不是 Body、查询参数不用写了
✅ 而是:Body 和 查询字符串 参数 照样要原样写、照样要带
✅ 额外再多带一个:请求头里放 Sign 签名
接口签名逻辑是:
- 你发请求时
- URL 查询参数 照样要写
m=u&c=login&a=dorun - Body 表单参数 照样要填
username、password、backurl、invite
这些参数一个都不能省,该传还得传
-
只是多做了一件事:
把所有这些 URL+Body 参数
按规则排序 → 拼接 → 加appid/随机号/时间戳 → MD5加密
生成一串 Sign 签名
-
最后额外在请求头里带上:
Authorization: Sign 生成的那串MD5大写签名
打个最简单比方
你去车站坐车:
- 你本身人要去、身份证要带(相当于:URL参数、Body参数照样传)
- 还要额外多带一张车票(相当于:请求头里带 Sign 签名)
不能说:有车票了,人跟身份证就不用了 ❌
必须:人+身份证照常带,再额外多带车票 ✅
5.8 疑问2🤔那接口签名BeanShell脚本我们是写在哪里呢?是写在后置处理器还是预处理器?
答: 直接给你标准答案
写在 HTTP请求 的 前置处理器 里
✅ 接口签名脚本 → 必须放在前置处理器(BeanShell 预处理器)
为什么放前置处理器?不放后置处理器?
前置处理器:发接口请求 之前 先执行
后置处理器:发完接口请求、拿到响应后 才执行
接口签名的逻辑:
- 先发请求之前,必须先把参数排序、拼接、算出 Sign
- 把 Sign 生成好,存成 JMeter 变量
${sign} - 然后再发 HTTP 请求,请求头引用
${sign}
顺序必须是:
预处理器执行(算签名) → 再发接口请求
后置处理器是请求都发完了才跑,那时候签名都晚了,根本没用。
结构图
HTTP请求
├─ 前置处理器(BeanShell预处理器) ← 写签名脚本,在这里生成sign
└─ HTTP信息头管理器 ← 引用 ${sign} 带到请求头
5.9 总结
一句话,上述折腾下来:
接口签名,本质就是只针对「接口业务
参数」做加密校验
三、写在最后
🎯 看到这里,辛苦啦!歇一歇,喝口水,让眼睛放松一下
这篇笔记我写了很久,如果对你有哪怕一点点帮助,请点一下「关注」 。
后面我还会继续分享很多自学干货笔记,都是自己边学边总结的,依然会是零基础能看懂、每一步都截图的那种详细笔记 。
我是学生,没什么能送你的,只能保证每一篇都认真写、不藏私。
我能给的,就是一篇一篇亲手整理的、零基础也能看懂的干货笔记。
你的每一次关注,都是我继续写下去的动力。
谢谢你的时间,我们下一篇笔记见 👇
(如果这篇笔记有写错的地方,也请一定在评论区告诉我,我会第一时间修改)

兄弟们,到这里咱们精通JMeter接口测试 就完结撒花了 🌸🌸🌸
最后,要感谢每个点赞、评论、催更的兄弟 ------ 没有你们的正反馈,这个系列很难坚持到完结 ❤️
下一系列见咯,兄弟们~~
咱们下一系列将 拿下 Postman

