前面我们有讲过如何进行API的安全控制,其中包括数据加密,接口签名等内容。详细可以参考我的这两篇文章《前后端API交互如何保证数据安全性》。
在签名部分,通过时间戳的方式来判断当前请求是否有效,目的是为了方式接口被多次使用。但是这样并不能保证每次请求都是一次性的,今天给大家介绍下如何保证请求一次性?
首先我们来回顾一些时间戳判断的原理:客户端每次请求时,都需要进行签名操作,签名中会加上signTime参数(当前请求时间戳)。HTTP请求从发出到达服务器的正常时间不会很长,当服务器收到HTTP请求之后,首先进行签名检查,通过之后判断时间戳与当前时间相比较,是否超过了一定的时间,这个时间我们可以自行决定要多长,比如1分钟,2分钟都可以,时间长点可以防止客户端和服务器时间不一致的问题,如果超过了则认为是非法的请求。
假设我们的请求有效期是1分钟,如果黑客得到了我们的请求地址,在1分钟之内是可以重复请求多次的,因为这种方式不能保证请求仅一次有效。
基于nonce的方式可以解决重复使用的问题,最开始知道nonce是在广点通的接口中看到的,如下图所示:
可以看到腾讯这边的接口也是基于时间戳和nonce来控制的。
nonce是随机字符串,每次请求时都要保证不同,你可以用uuid,可以用别的算法,也可以用随机加时间戳等等,只要不重复就行。
在后端我们验证的时候,将nonce参数存储起来,至于是存储在本地内存中,数据库中,redis,布隆过滤器中这就看你自己的需求了。
处理HTTP请求时,首先判断该请求的nonce参数是否在存在,如果存在则认为是非法请求。如果不存在则是合法请求,让后将该nonce存储起来,防止下次重复使用。
这种方式的弊端也很明显,那就是nonce的存储会越来越大,验证nonce是否存在的时间会越来越长。
如何解决存储问题?
可以用时间戳+nonce同时使用,相互配合,取长补短。
首先我们根据时间戳判断是否超过了一定的时间范围,如果超过了就直接拒绝,没有超过继续验证nonce是否使用过。
nonce没使用,存储起来,记录一个存储时间,通过定时任务去清除超过了时间戳验证的时间的nonce。这样的话我们只需要存储固定时间内的nonce数据,因为时间长的已经被时间戳的判断给拦截了,nonce的验证只需要验证时间有效期内的即可。
伪代码如下:
// 1.1获取签名时间,跟当前时间做对比,判断是否超出范围
Long signTime = 1XXXXL;
Long curTime = System.currentTimeMillis();
if (curTime - signTime > 60000) {
// 超出了
return;
}
// 1.2获取nonce判断是否存在,以redis举例
String nonce = "XXXXXX";
if (redisTemplate.hasKey(nonce)){
//已经使用过了
} else {
//没有使用,存储起来,设置过期时间为当前访问时间+判断请求过期的时间
//这样就不用定时任务去清除之前的nonce,利用redis自动清除
redisTemplate.opsForValue().set(nonce, "", 1, TimeUnit.MINUTES);
}
复制代码