最近有个项目需要接口测试,接口都是通过 aksk 认证对请求进行加密签名,分享一下 Jmeter 解决方案。
AK/SK 认证
通过 API 网关向下层服务发送请求时,必须使用 AK(Access Key ID)、SK(Secret Access Key) 对请求进行签名。
说明:
AK(Access Key ID)
:访问密钥 ID。与私有访问密钥关联的唯一标识符;访问密钥 ID 和私有访问密钥一起使用,对请求进行加密签名。
SK(Secret Access Key)
:与访问密钥 ID 结合使用的密钥,对请求进行加密签名,可标识发送方,并防止请求被修改。
PS:对于 AKSK 大概知道怎么回事就 OK,一般调用接口有如下两种认证方式,您可以任选其中一种进行认证鉴权。
• Token 认证 :通过 Token 认证调用请求。
• AK/SK 认证 :通过 AK(Access Key ID)/SK(Secret Access Key) 加密调用请求。AK/SK 认证安全性更高。
Python 实现 aksk 加密的代码如下 (getAksk.py):
#encoding:utf-8
import requests
import hmac
import base64,sys
from hashlib import sha1
ak = "your-ak"
sk = "your-sk"
HOST = "127.0.0.1:5201"
METHOD = "POST"
PATH = "/getToken"
CONTENT_TYPE = "application/json"
data= '{"flag":"test","appId":"001"}'
def sign():
raw1 = "{} {}\n".format(METHOD, PATH)
raw2 = "Host: {}\n".format(HOST)
raw3 = "Content-Type: {}\n".format(CONTENT_TYPE)
raw4 = "\n"
print raw1
print raw2
print raw3
print raw4
print data
hmaccode = hmac.new(sk,"{}{}{}{}{}".format(raw1, raw2, raw3, raw4, data),sha1).digest()
b64code = base64.b64encode(hmaccode)
b64code = b64code.replace('/', '_').replace('+', '-')
print b64code
code = "mt {}:{}".format(ak,b64code)
print '"aksk":"{}"'.format(code)
return code
def main():
req = requests.Session()
headers = {
"Content-type": CONTENT_TYPE,
"Authorization": sign()
}
res = req.post("http://{}{}".format(HOST,PATH),data=data,headers=headers)
print headers
print res.content
if __name__ == "__main__":
main()
假设我们的接口信息如下:
POST /getToken
Host: 127.0.0.1:5201
Content-Type: application/json
请求 Body:{"flag":"test","appId":"001"}
我们发请求时需要在Headers 中添加一个 Authorization,Authorization 的值是根据 ak,sk 和上面的四行信息生成的 (参考代码中hmaccode
的值如何生成),也就是说我们的请求 URL 和 Body 等是跟 Authorization 相关联的,如果我们抓包想改接口的 Body,就必须把 Headers 的 Authorization 也相应修改,但是修改 Authorization 我们需要 ak 和 sk 的值,但是一般 sk 是不会暴露出来的。所以拿不到 sk 我们就无法发送跟请求 body 匹配的 Authorization,接口请求就会因为授权失败而被服务器拒绝。
Jmeter 调用 Python 代码我们会用到一个 sampler:
OS Process Sampler
OS Process Sampler
:可以用来启动一个可执行程序,由于是通过命令行方式启动,所以我们可以用任何语言编写一个测试用的可执行程序 (比如 Linux 的 sh 脚本)。在该可执行程序中调用我们的接口,并把返回的原始数据输出而交由 JMeter 做后续解析判断。
OS Process Sampler默认路径是 jemter 的 bin 目录,可在 Working directory 中定义当前工作目录,如果可执行文件有参数需要传入,在 Command parameters 中添加即可 (后面会说到这个的使用),Timeout configuration 中定义可执行文件的超时时间,单位 (ms)。
我们编写一个 getAksk.sh 脚本,调用 Python 的 getAksk.py 文件:
pwd
python /Users/grizz/getAksk.py
执行结果:先输出工作目录,再执行 py 文件,执行 getAksk.sh 在 jmeter 中的输出如下:
其实就是在 jmeter 中打印出 py 文件的运行日志,Pycharm 中的运行 getAksk.py 文件的输出如下,日志信息是一样的:
于是我们知道了,jmeter 可以运行 sh 脚本,并把 sh 脚本的输出日志显示在 jmeter 的响应数据中,供 jmeter 进行解析调用。意思就是 sh 脚本能做的,jmeter 就可以调着做,那 jmeter 调用 Python 代码就只是这个功能里面很少的一部分。网上对 jmeter 中OS Process Sampler的说明使用的资料很少,我开个调 Python 的头,大家可以根据业务需要自行扩展使用。
由于正则表达式提取器可以提取 jmeter 的输出,我们添加正则的配置如下:
正则表达式:"aksk":"(.+?)"
使用${aksk}
即可使用 Authorization 的值mt your-ak:3j6UUc6x_dQhYgxlSX_AajCY1Yk=
然后在HTTP Header Manager中调用
则/getToken接口请求时就会带上Authorization: mt your-ak:3j6UUc6x_dQhYgxlSX_AajCY1Yk=
如果文章只说 jmeter 调 Python 代码,那可能已经写完了,因为 jmeter 可以调 sh 脚本,sh 可以执行 Python 文件输出日志。但我们讲的是 Jmeter 运行 Python 代码进行 AK/SK 认证,上面的 py 文件中写死了接口的 URL 和 Body,正常的我们不可能一个接口对应一个 py 文件,其实不同的接口,主要有两个参数是不一样的,那就是接口的 URL 和 Body,于是我们考虑把这两个参数进行参数化。
但是我们的执行流程是 jmeter 调 sh 脚本,sh 执行 py 文件,那么我们先要通过 jmeter 把接口的 URL 和 Body 传给 sh 脚本,sh 脚本再把接口的 URL 和 Body 传给 py 文件去执行。
有些编程基础的同学应该知道,执行 Python 文件时可以接收参数,如
控制台执行:python /Users/grizz/getAksk.py /getToken {"flag":"test","appId":"001"}
传入参数请求 URL/getToken
和请求 Body{"flag":"test","appId":"001"}
给 Python 文件,Python 使用 sys.argv可以接收传入的参数。
getAksk.py 文件添加几行打印日志:
控制台输出如下:
我们可以看出当传入接口的 URL 和 Body 时:
sys.argv= ['getAksk.py', '/getToken', '{flag:test,appId:001}']
sys.argv就是将程序本身和给程序参数返回一个 list,这个 list 中的索引为 0 的就是程序本身,list 中的索引为 1 则为传入的第一个参数,依次类推。
明人不说暗话 (我喜欢你),sh 脚本运行时也支持传入参数。
getAksk.sh 脚本文件改成:
pwd
python /Users/grizz/getAksk.py ${1} ${2}
运行:./getAksk.sh /getToken "{"flag":"test","appId":"001"}"
看样子我们只要把OS Process Sampler的 Command 由
/Users/grizz/jmeter/testcase/getAksk.sh
改成:
/Users/grizz/jmeter/testcase/getAksk.sh /getToken "{"flag":"test","appId":"001"}"
查看结果树种 sampler 的请求和响应数据:
显然报错了,我们的打开方式是错误的,哪里错了呢,是我们利用 OS Process Sampler 给 sh 脚本传参的方式错了,正确的打开方式如下:
再看看查看结果树中 sampler 的请求和响应数据
最后就只剩下一个问题了,HTTP 请求 sampler 中的 URL,Body 和 OS Process Sampler 的 Command parameter 中一致,我尝试过对接口的请求 body 进行提取,但是我们需要改的是请求的 headers,我们抓到请求 Body 时 headers 也确定了,于是只有把 URL 和 Body 放入文本中。
getAksk.csv 文件 (我设置成以?分隔读取):
/getToken?{"flag":"test","appId":"001"}
csvRead 函数使用可参考:Jmeter 接口自动化 - 脚本数据分离实例
${DATA}
=/Users/grizz/jmeter/testcase (getAksk.csv 文件目录,可自行设置)
${__eval(${__CSVRead(${DATA}/getAksk.csv,0)})}
= /getToken
${__eval(${__CSVRead(${DATA}/getAksk.csv,0)})}
= {"flag":"test","appId":"001"}
OS Process Sampler设置
欢迎交流指正,感谢阅读。