接口签名实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口签名实现相关的知识,希望对你有一定的参考价值。

参考技术A

在为第三方系统提供接口的时候,肯定要考虑接口数据的安全问题,比如数据是否被篡改,数据是否已经过时,请求是否唯一,数据是否可以重复提交等问题。其中数据是否被篡改相对重要。

请求携带参数 appid sign ,只有拥有合法的身份appid和正确的签名sign才能放行。这样就解决了身份验证和参数篡改问题,即使请求参数被劫持,由于获取不到secret( 仅作本地加密使用,不参与网络传输 ),无法伪造合法的请求。

只使用appid和sign,虽然解决了请求参数被篡改的隐患,但是还存在着重复使用请求参数伪造二次请求的隐患。

nonce指 唯一的随机字符串 ,用来标识每个被签名的请求。通过为每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用(记录所有用过的nonce以阻止它们被二次使用)。

然而,对服务器来说永久存储所有接收到的nonce的代价是非常大的。可以使用 timestamp来优化nonce的存储

假设允许客户端和服务端最多能存在10分钟的时间差,同时追踪记录在服务端的nonce集合。当有新的请求进入时,首先检查携带的timestamp是否在10分钟内,如超出时间范围,则拒绝,然后查询携带的nonce,如存在(说明该请求是第二次请求),则拒绝。否则,记录该nonce,并删除nonce集合内时间戳大于10分钟的nonce(可以使用redis的expire,新增nonce的同时设置它的超时失效时间为10分钟)。

对服务端而言,拦截请求用AOP切面或者用拦截器都行,如果要对所有请求进行拦截,可以直接拦截器处理(拦截器在切面之前,过滤器之后,具体在springmvc的dispather分发之后)。

过滤器→拦截器→切面的顺序:

其中,需要放在请求头的字段: appid timestamp nonce signature

对各种类型的请求参数,先做如下拼接处理:

如果存在多种数据形式,则按照path、query、form、body的顺序进行再拼接,得到所有数据的拼接值。

上述拼接的值记作 Y。

X=”appid=xxxnonce=xxxtimestamp=xxx”

最终拼接值=XY。最后将最终拼接值按照一个加密算法得到签名。

虽然散列算法会有推荐使用 SHA-256、SHA-384、SHA-512,禁止使用 MD5。但其实签名这里用MD5加密没多大问题,不推荐MD5主要是因为,网络有大量的MD5解密库。

实现可以分以下几步:

自定义的缓存有body参数的HttpServletRequest:

过滤器中替换自定义的RequestServlet:

添加过滤器的配置以及注意顺序:

由于Zuul自带默认的过滤中,有已经对body处理过的(FormBodyWrapperFilter),所以在Zuul中处理签名,只需添加一个过滤器即可如下。

java接口签名(Signature)实现方案
开放API接口签名验证,让你的接口从此不再裸奔

JMeter BeanShell 实现接口签名验签及加解密

在这里插入图片描述
在利用JMeter进行接口测试或者性能测试的时候,我们需要处理一些复杂的请求,比如对接口请求参数进行签名,加密,响应数据的验签及解密,以及接口公共参数的处理,此时就需要利用BeanShell脚本了,关于BeanShell的使用小伙伴们可以查看网上相关文章。今天主要和大家分享下接口签名,验签,加解密,以及处理公共参数的例子,希望能帮助到小伙伴们。

一,思路

约定:约定接口有统一的请求及响应格式,如:

请求协议公共部分


响应协议公共部分

基于此约定,我们才能进一步统一处理。

  • 引入外部签名及加解密工具包
  • JMeter的HTTP请求->请求参数中只填写业务对象(parameter)
  • 利用前置处理器(BeanShell PreProcessor),组装公共请求对象->对业务参数对象进行加密->签名
  • 利用后置处理器(BeanShell PostProcessor)对响应报文进行验签->解密。

二,实现

整体效果如下图:

看完这篇内容后,相信以下两件事,也会对你的个人提升有所帮助:

1、 点赞,让更多人能看到这篇文章,同时你的认可也会鼓励我创作更多优质内容。

2、 让自己变得更强:想一想,如果你想在测试这个行业一直做下去,你的经验和测试技术是远远不够的,你需要进阶,你需要丰富你的技术栈!还等什么!

关于如何建立测试计划,线程组就不用一一描述了,这里只关注核心功能实现。

HTTP请求参数(parameter)部分,如:

{
    "idCardNo":"511622198312241918",
    "name":"Leo"
}

接口调用前置处理器-签名/加密(BeanShell PreProcessor),BeanShell代码如下:

//引入依赖
import com.javacoo.service.base.security.util.SignUtil;
import com.javacoo.service.base.security.util.SecurityUtil;
import com.javacoo.service.base.utils.WebUtil;
import com.javacoo.service.base.utils.FastJsonUtil;
import com.javacoo.service.base.BaseRequest;
import java.util.Calendar;
import java.util.Map;
import org.apache.jmeter.config.Arguments; 

//开始处理
log.info("接口调用前置处理器-签名/加密相关处理");
Arguments args = sampler.getArguments(); 

//获取请求参数
String body = args.getArgument(0).getValue();
log.info("业务参数:{}",body);

//获取签名所需参数
String appKey = "${appKey}";
String secretkey = "${secretkey}";
String nonce = WebUtil.genTransSn();
String transactionSn = WebUtil.genTransSn();
Long timestamp = Calendar.getInstance().getTimeInMillis();

//加密
Map bodyMap = FastJsonUtil.stringToCollect(body);
log.info("params:{}",bodyMap);
for(Map.Entry entry : bodyMap.entrySet()){
    entry.setValue(SecurityUtil.encryptDes(entry.getValue(),secretkey));
}
body = FastJsonUtil.toJSONString(bodyMap);
log.info("加密后业务参数:{}",body);

//签名
String sign = SignUtil.clientSign(body,nonce,timestamp.toString(),secretkey);
log.info("sign:{}",sign);

//组装接口请求对象
BaseRequest baseRequest = new BaseRequest();
baseRequest.setAppKey(appKey);
baseRequest.setNonce(nonce);
baseRequest.setTimestamp(timestamp);
baseRequest.setTransactionSn(transactionSn);
baseRequest.setSign(sign);
baseRequest.setParameter(FastJsonUtil.toBean(body));
//转换为JSON字符串
String reqBody = FastJsonUtil.toJSONString(baseRequest);
log.info("reqBody:{}",reqBody);

//重置参数值
args.getArgument(0).setValue(reqBody);

接口调用后置处理程序-验证签名/解密(BeanShell PostProcessor),BeanShell代码如下:

//引入依赖
import com.javacoo.service.base.security.util.SignUtil;
import com.javacoo.service.base.BaseResponse;
import com.javacoo.service.base.utils.FastJsonUtil;
import org.apache.commons.lang3.StringUtils;

//开始处理
log.info("接口调用后置处理器-验证签名");
String responseData = prev.getResponseDataAsString();
log.info("返回数据:{}",responseData);
BaseResponse baseResponse = FastJsonUtil.toBean(responseData, BaseResponse.class);
if(StringUtils.isBlank(baseResponse.getSign()) || baseResponse.getData().get() == null){
    return;
}
//转换
String s = FastJsonUtil.toJSONString(baseResponse.getData().get());
log.info("请求返回业务json:{}",s);
log.info("请求返回签名:{}",baseResponse.getSign());
String secretkey = "${secretkey}";

//验证签名
if (SignUtil.cloudVerifySign(baseResponse.getSign(), s,baseResponse.getTransactionSn(),baseResponse.getTimestamp().toString(), secretkey)) {
       log.info("返回数据合法");
} else {
       log.info("返回数据被篡改");
}
//解密,TODO

三,注意事项及问题

JMeter不支持java1.5以后的语法,不支持泛型,如要使用则需要封装成JAR包。

一些信息

路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo
QQ群:902061117
作者/微信:test6738

最后:【可能给予你助力的教程】


这些资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。

关注我的微信公众号:【伤心的辣条】免费获取~

我的学习交流群:902061117 群里有技术大牛一起交流分享~

如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!

好文推荐:

35岁之后软件测试工程师靠什么养家?我能继续做测试!

App公共测试用例梳理

Python简单?先来40道基础面试题测试下

阿里二黑叹息:越来越多的年轻人从职场撤退了?

从一名开发人员转做测试的一些感悟

以上是关于接口签名实现的主要内容,如果未能解决你的问题,请参考以下文章

为啥继承具有名称签名的接口成员的 C# 抽象类至少需要实现其中一个?

使用裸函数签名和其他字段实现 TypeScript 接口

显示实现接口

安全优雅的RESTful API签名实现方案(手机端)

jmeter实现调用要求签名的接口

“相同的 JVM 签名”实现包含 getter 方法的 kotlin 接口