接口鉴权与动态签名概述
为了保证接口的安全性,通常都会加入客户端身份鉴权的机制。一种是基于cookie的身份验证,当测试这种接口时,常用方式是使用浏览器登陆后,调用Chrome的F12开发者工具,任意选取一个接口从标头中的Cookie复制粘贴到接口测试工具中,就可以快乐玩耍了。风险是根据服务器端的设置cookie可能在一段时间后过期,只要保持浏览器窗口活跃或者测试工具的使用状态,一般都不会对测试有太大影响。
![](https://img.haomeiwen.com/i16460658/7952c237bfc741f7.png)
另一种签名方式,是通过拼接每次请求里的参数再使用MD5算法生成签名,如果请求内容每次都不变,那么和第一种就没有本质区别,测试时sign设置一个固定的值就可以。但是当请求里含有可变参数,例如当前时间戳或者有随机生成的salt值时,每次请求的签名都会发生动态变化,那么可以按照本文介绍的设置动态签名通用思路进行配置。
接口功能、性能测试工具
签名的通用步骤
两款接口测试工具,功能测试代表ApiFox和性能测试代表Jmeter,配置动态签名的步骤。
针对每个请求计算签名的算法,基本上由两步组成,首先拼接出字符串,再对字符串签名。例如百度翻译签名算法。
下面是一个简单的HTTP POST请求例子,假设拼接字符串的要求是将API和请求body中按照key:value拼接成一个字符串,第一步先拼接出字符串“api/open/testapi+appid123+param1CD+time123+密钥”。第二步对得到的字符串用MD5算法计算得到sign=9E47C32D0B70A398E1B7EBCBEC10F5FD。
http://localhost/open/testapi
{
"appid": "123",
"time": "1681199355",
"param1": "CD"
}
为了方便将签名应用在不同场景上,将上述算法写成一个外部程序进行调用,例如jar包,变量作为运行参数传入。以上面的请求为例,java -jar dynamic-sign.jar "/open/testapi" "{"appid":"123","time":"1681199355","param1":"CD"}"
为了方便打出一个包含外部依赖,可直接执行的jar包,在pom.xml使用下面maven-shade-plugin插件,mvn package就行了。执行java -jar时会执行主类中的main方法。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>主类的full path</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
有了签名jar包后,在apifox和jmeter中配置略有区别
Apifox中配置签名
-
将jar包放到外部程序的目录中
image.png
-
项目配置-公共脚本-新建按钮
思路是从当次请求中获取url和body,调用jar包,计算出签名,并设置到环境变量中。请求时通过{{SIGN}}来使用签名。依赖工具内置的几个命令pm.request, pm.execute,pm.environment。
var queryParams = pm.request.url.query
var pathArray = pm.request.url.path
var path = pathArray.join('/')
var body = pm.request.body.raw
//预防body中有变量,达到取替换后的值的效果
body = pm.variables.replaceIn(body)
var timestamp = pm.environment.get("TIMESTAMP")
try{
var jarResult = pm.execute('dynamic-sign.jar',[path, timestamp, body], { windowsEncoding: 'utf-8' })
jarResult = jarResult.trim()
console.log('output sign:'+jarResult)
pm.environment.set("SIGN", jarResult);
}catch(e){
console.error(e.message)
}
时间戳要求是当前时间,所以也需要添加一个获取当前时间的脚本。用js函数取得当前时间后,设置到环境变量中
var timestamp = new Date().getTime();
pm.environment.set("TIMESTAMP", timestamp);
-
当公共脚本添加好后,配置接口调用脚本获取sign
项目-接口组-前置操作,添加前置-公共脚本
image.png
-
接口-修改文档中,使用变量
image.png
Jmeter中配置签名
- 将jar包放到jmeter的路径apache-jmeter-5.5\lib\ext中后,再启动jmeter程序
-
思路是利用BeanShell前置处理器,调用jar包计算sign,放入全局变量中。BeanShell是直接调用java的类,请求的api和body通过sampler.getArguments获取,全局变量传递依赖vars.put和vars.get,测试计划-线程组-接口,添加一个BeanShell Preprocessor
image.png
脚本内容为
import com.test.util.MyEncrypt;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.Argument;
String timestamp = vars.get("TIMESTAMP").trim();
Arguments args = sampler.getArguments();
Argument arg = args.getArgument(0);
String body = arg.getValue().trim();
String path = sampler.getPath();
path = path.substring(1);
String sign = MyEncrypt.genSign(path, timestamp, body);
//log.info("sign:"+sign);
vars.put("SIGN", sign);
设置当前时间时,使用Jmeter的内置函数time如下图。使用时用${TIMESTAMP}
![](https://img.haomeiwen.com/i16460658/edd216942ac84a1d.png)
-
最后在HTTP Header Manager中使用变量
image.png
签名中可能遇到的坑
如果出现请求端发送的签名和服务端计算出的签名不一致,导致鉴权不通过,看看是否符合下面的坑
fastjson与gson
签名算法中间步骤可能会涉及字符串、JSON对象、Map对象的转换,对于简单结构{"a":"1"},签名结果没有区别。如果有数组{"a":["1"]}可能会签出不同的结果。下面的2种方案,挨个试试
//方案一
Map<String, Object> paramMap = new Gson().fromJson(bodyStr, Map.class);
//方案二
JSONObject body = JSONObject.parseObject(bodyStr);
body.forEach( (k,v) -> map.put(k, String.valueOf(v)));
数字
例如使用的参数是{"a":1}签名结果不正确,换成{"a":"1"}试试
Windows系统下斜杠/开头的路径
java -jar xx.jar "/open/apitest" 签名不正确,添加debug日志后发现实际传入的参数被加上了前缀类似C:/xxx/open/apitest。解决方法是参数中不传第一个斜杠java -jar xx.jar "open/apitest",在算法内部添加斜杠
总结
为请求配置动态签名的思路为
- 编译出可执行jar包
- 参考工具文档,获取当前请求的API/body等参数,作为jar包参数,来生成签名
- 将签名设置为全局变量,供请求发出的时候使用
网友评论