一、 背景:

最近有个项目需要接口测试,接口都是通过 aksk 认证对请求进行加密签名,分享一下 Jmeter 解决方案。

二、 AK/SK:

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,接口请求就会因为授权失败而被服务器拒绝。

3.1、OS Process Sampler 的使用

Jmeter 调用 Python 代码我们会用到一个 sampler:
OS Process Sampler


OS Process Sampler
:可以用来启动一个可执行程序,由于是通过命令行方式启动,所以我们可以用任何语言编写一个测试用的可执行程序 (比如 Linux 的 sh 脚本)。在该可执行程序中调用我们的接口,并把返回的原始数据输出而交由 JMeter 做后续解析判断。

OS Process Sampler默认路径是 jemter 的 bin 目录,可在 Working directory 中定义当前工作目录,如果可执行文件有参数需要传入,在 Command parameters 中添加即可 (后面会说到这个的使用),Timeout configuration 中定义可执行文件的超时时间,单位 (ms)。

3.2、OS Process Sampler 调用 sh 脚本

我们编写一个 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=

3.3、OS Process Sampler 参数化调用 sh 脚本

如果文章只说 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设置

欢迎交流指正,感谢阅读。


↙↙↙阅读原文可查看相关链接,并与作者交流