API接口设计:防参数篡改+防二次请求

API接口由于需要供第三方服务调用,所以必须暴露到外网,并提供了具体请求地址和请求参数

为了防止被第别有用心之人获取到真实请求参数后再次发起请求获取信息,需要采取很多安全机制

1.首先: 需要采用https方式对第三方提供接口,数据的加密传输会更安全,即便是被破解,也需要耗费更多时间

2.其次:需要有安全的后台验证机制【本文重点】,达到防参数篡改+防二次请求

主要防御措施可以归纳为两点:

  • 对请求的合法性进行校验
  • 对请求的数据进行校验

防止重放攻击必须要保证请求仅一次有效 需要通过在请求体中携带当前请求的唯一标识,并且进行签名防止被篡改。 所以防止重放攻击需要建立在防止签名被串改的基础之上。

请求参数防篡改

采用https协议可以将传输的明文进行加密,但是黑客仍然可以截获传输的数据包,进一步伪造请求进行重放攻击。如果黑客使用特殊手段让请求方设备使用了伪造的证书进行通信,那么https加密的内容也将会被解密。 在API接口中我们除了使用https协议进行通信外,还需要有自己的一套加解密机制,对请求参数进行保护,防止被篡改。 过程如下:

  1. 客户端使用约定好的秘钥对传输参数进行加密,得到签名值signature,并且将签名值也放入请求参数中,发送请求给服务端
  2. 服务端接收客户端的请求,然后使用约定好的秘钥对请求的参数(除了signature以外)再次进行签名,得到签名值autograph。
  3. 服务端对比signature和autograph的值,如果对比一致,认定为合法请求。如果对比不一致,说明参数被篡改,认定为非法请求。

因为黑客不知道签名的秘钥,所以即使截取到请求数据,对请求参数进行篡改,但是却无法对参数进行签名,无法得到修改后参数的签名值signature。 签名的秘钥我们可以使用很多方案,可以采用对称加密或者非对称加密。

防止重放攻击

基于timestamp的方案

每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。因为一次正常的HTTP请求,从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间相比较,是否超过了60s,如果超过了则认为是非法的请求。

一般情况下,黑客从抓包重放请求耗时远远超过了60s,所以此时请求中的timestamp参数已经失效了。 如果黑客修改timestamp参数为当前的时间戳,则signature参数对应的数字签名就会失效,因为黑客不知道签名秘钥,没有办法生成新的数字签名。

但这种方式的漏洞也是显而易见的,如果在60s之后进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效。

基于nonce的方案

nonce的意思是仅一次有效的随机字符串,要求每次请求时,该参数要保证不同,所以该参数一般与时间戳有关,我们这里为了方便起见,直接使用时间戳的16进制,实际使用时可以加上客户端的ip地址,mac地址等信息做个哈希之后,作为nonce参数。 我们将每次请求的nonce参数存储到一个“集合”中,可以json格式存储到数据库或缓存中。 每次处理HTTP请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。

nonce参数在首次请求时,已经被存储到了服务器上的“集合”中,再次发送请求会被识别并拒绝。 nonce参数作为数字签名的一部分,是无法篡改的,因为黑客不清楚token,所以不能生成新的sign。

这种方式也有很大的问题,那就是存储nonce参数的“集合”会越来越大,验证nonce是否存在“集合”中的耗时会越来越长。我们不能让nonce“集合”无限大,所以需要定期清理该“集合”,但是一旦该“集合”被清理,我们就无法验证被清理了的nonce参数了。也就是说,假设该“集合”平均1天清理一次的话,我们抓取到的该url,虽然当时无法进行重放攻击,但是我们还是可以每隔一天进行一次重放攻击的。而且存储24小时内,所有请求的“nonce”参数,也是一笔不小的开销。

基于timestamp和nonce的方案

nonce的一次性可以解决timestamp参数60s的问题,timestamp可以解决nonce参数“集合”越来越大的问题。 防止重放攻击一般和防止请求参数被串改一起做,请求的Headers数据如下图所示。

我们在timestamp方案的基础上,加上nonce参数,因为timstamp参数对于超过60s的请求,都认为非法请求,所以我们只需要存储60s的nonce参数的“集合”即可。

最终实现代码(网关层验证):

 1 public class APIAuth extends ZuulFilter {
 2     @Override
 3     public String filterType() {
 4         return "pre";
 5     }
 6 
 7     @Override
 8     public int filterOrder() {
 9         return 0;
10     }
11 
12     @Override
13     public boolean shouldFilter() {
14         return true;
15     }
16 
17     @Override
18     public Object run() {
19         RequestContext ctx = RequestContext.getCurrentContext();
20         HttpServletRequest request = ctx.getRequest();
21 
22         String token = request.getHeader("token");
23         String timestamp = request.getHeader("timestamp");
24         String nonce = request.getHeader("nonce");
25         String sign = request.getHeader("sign");
26 
27         //时间限制配置
28         int timeLimit = 60;
29 
30         ctx.setSendZuulResponse(false);
31 
32         //请求头参数非空验证
33         if (StringUtils.isEmpty(token) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce) || StringUtils.isEmpty(sign)) {
34             ctx.setResponseBody(JSON.toJSONString(new Result("-1", "请求头参数不正确")));
35             return null;
36         }
37 
38         //请求时间和现在时间对比验证,发起请求时间和服务器时间不能超过timeLimit秒
39         if (StringUtils.timeDiffSeconds(new Date(), timestamp) > timeLimit) {
40             ctx.setResponseBody(JSON.toJSONString(new Result("-1", "请求发起时间超过服务器限制")));
41             return null;
42         }
43 
44         //验证用户信息
45         UserInfo userInfo = UserInfoUtil.getInfoByToken(token);
46         if (userInfo == null) {
47             ctx.setResponseBody(JSON.toJSONString(new Result("-1", "错误的token信息")));
48             return null;
49         }
50 
51         //验证相同noce的请求是否已经存在,存在表示为重复请求
52         if (NoceUtil.exsit(userInfo, nonce)) {
53             ctx.setResponseBody(JSON.toJSONString(new Result("-1", "重复的请求")));
54             return null;
55         } else {
56             //如果noce没有在缓存中,则需要加入,并设置过期时间为timeLimit秒
57             NoceUtil.addNoce(userInfo, nonce, timeLimit);
58         }
59 
60         //服务器生成签名与header中签名对比
61         String serverSign = SignUtil.getSign(userinfo, token, timestamp, nonce, request);
62         if (!serverSign.equals(sign)) {
63             ctx.setResponseBody(JSON.toJSONString(new Result("-1", "错误的签名信息")));
64             return null;
65         }
66 
67         ctx.setSendZuulResponse(true);
68         return null;
69     }
70 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏拂晓风起

Flex Actionscript 3 flash游戏 键盘 连招 连按 combo 同时按 事件处理

13450
来自专栏上善若水

003互联网网络技术之WireShark过滤语法

less than 小于 < lt 小于等于 le 等于 eq 大于 gt 大于等于 ge 不等 ne

14760
来自专栏决胜机器学习

设计模式专题(十)——观察者模式

设计模式专题(十)——观察者模式 (原创内容,转载请注明来源,谢谢) 一、概述 观察者模式(Observer),又称做发布-订阅模式(Publish/Subs...

36990
来自专栏程序员互动联盟

【专业技术】Android安全嘛?

安卓有一套自己的安全权限机制,大部分来自linux的权限机制,某些地方也做了延伸,比如linux中的用户概念,在安卓上来说就相当于app。对于一些刚学习安卓的同...

42090
来自专栏李家的小酒馆

struts2面试整理

struts2的工作原理 客户端发送请求 经过一系列的过滤器 FilterDispatcher通过ActionMapper来决定这个REquest需要调用的Ac...

20400
来自专栏salesforce零基础学习

salesforce零基础学习(八十九)使用 input type=file 以及RemoteAction方式上传附件

在classic环境中,salesforce提供了<apex:inputFile>标签用来实现附件的上传以及内容获取。salesforce 零基础学习(二十四)...

26110
来自专栏程序员互动联盟

【专业技术】Android如何保证安全?

存在问题: 那么多小伙伴想root,root后好处多多你懂的,那么开发的小伙伴最想关心的是安全机制问题。 解决方案: 我们就以此来了解一下Android 安全...

40460
来自专栏Java3y

Struts2【拦截器】

什么是拦截器 拦截器Interceptor…..拦截器是Struts的概念,它与过滤器是类似的…可以近似于看作是过滤器 为什么我们要使用拦截器 前面在介绍Str...

33050
来自专栏扎心了老铁

Redis实现分布式锁

之前写过一篇博客,里面吭哧吭哧半天,使用Redis实现了一个分布式锁。 今天闲来没事看源码,突然发现redis set命令的用法可以直接指定nx和ex,文档中没...

39160
来自专栏散尽浮华

Python-Socket

socket通常也称作套接字,用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过“套接字”向网络发出请求或者应答网络请求 socket既是一种特殊文件...

28370

扫码关注云+社区

领取腾讯云代金券