基于“请求重放”设计的签名方案

1:背景

最近在跟第三方做服务对接,第三方需要调用我们的服务接口查询数据,对于暴露出去的接口,安全是很重要的,所以在做接口校验的时候一定要加上签名,这样可以有效的保护我们的接口安全

2:加密

不可能让数据在网络中裸奔,所以对于接口参数或者返回数据我们都需要进行加密处理,而对于请求方(甲方)的要求,我们必须使用国秘SM4进行加密

3:签名

除了请求参数之外,我们还需要发送过来的数据中包含签名,在我们收到请求后判断签名是否合法,如果不合法就不处理,但是会有问题,如果黑客截取了请求进行一次次重发就行了,你服务端都是会通过的,因为没有改变你的签名,我只是重复发送请求而已

4:加时间戳

签名不仅仅只是一个约定好的字符串了,我们还要加上时间戳。在发送方那边在签名里面加上发送请求时候的时间戳,当服务端拿到请求后进行解密,获取时间戳,如果签名里面的时间戳距离当前时间大于1分钟或者2分钟(这个时间由自己定义),那么就不处理这个请求或者返回请求失败的信息

客户端:
//签名中约定好的字符串
String sign  = "adnchgsnsb";
//获取当前的时间戳
long timeMillis = System.currentTimeMillis();
//拼接签名
StringBuilder sb = new StringBuilder();
sb.append(sign).append('-').append(timeMillis);
//将签名进行加密
SM4.decode(sb.toString);
//调用远程服务
服务端:
//从参数中获取签名
String sign = sign;
//从签名中获取时间戳和约定的签名字符串
String[] strs = sign.split('-');
//获取签名的字符串
String singStr = strs.[0];
//判断签名字符串是否是约定好的
//获取时间戳
String time = strs[1];
//获取当前时间戳
long timeMillis = System.currentTimeMillis();
//判断二者的时间是否超出了限制
if(超出了) {
    return "请求超时";
}else{
  //处理请求
}

看着好像的确满足了我们的要求,但是在实际中还是会有问题,比如这个超时时间设置多少合适呢?? 在开始的时候我是设置成了10s,但是后面出现了大量的请求超时的异常,导致客户那边一直说,

后面也是没有办法,只能把时间设置的长了些,但是时间设置了长了,客户那边反应超时异常也几乎没有了,但是现在又有个问题了,假设你把时间设置成5分钟(没错,我就是设置了这么久),还是解决不了请求重放的问题,因为这个时间太长了,在你发送了一次请求之后,别人拿到你的请求有足够的时间去操作

5:增加Redis

这时候我们的签名中是有 签名字符串+时间戳二个部分组成,现在我们再加一个随机数,

客户端:
//签名中约定好的字符串
String sign  = "adnchgsnsb";
//获取当前的时间戳
long timeMillis = System.currentTimeMillis();
//获取随机数
String uuid = UUID.randomUUID().toString();
//拼接签名
StringBuilder sb = new StringBuilder();
sb.append(sign).append('-').append(timeMillis).append('-').append(uuid);
//将签名进行加密
SM4.decode(sb.toString);
//调用远程服务
服务端:
//从参数中获取签名
String sign = sign;
//从签名中获取时间戳和约定的签名字符串
String[] strs = sign.split('-');
//获取签名的字符串
String singStr = strs.[0];
//判断签名字符串是否是约定好的
//获取时间戳
String time = strs[1];
//获取当前时间戳
long timeMillis = System.currentTimeMillis();
//获取随机数
String uuid = strs[2];
//判断二者的时间是否超出了限制
if(超出了) {
    return "请求超时";
}else{
  //判断该请求之前是否处理过了
  Boolean uuid = redisUtil.get(uuid);
  //如果存在,说明该请求之前处理过,就不做处理
  if(uuid) {
     return "该请求已经处理过了";
  }else{
     //如果不存在,可以正常处理
     //处理请求
     //将该随机数放到redis中
     redisUtil.set(uuid,uuid);  //不设置过期时间
  }
}

此时我们看到redis中的key是没有设置过期时间的,这就会导致服务端redis中内存占用越来越高

6:优化

我们想想,redis中的key其实是可以设置过期时间的,而且这个时间的大小刚好可以设置成判断时间戳的那个时间,如果我们设置请求时间是5分钟,那我只要保证在这5分钟内不要再接受到这个请求就行,超出5分钟之后,如果还是这个请求的话,其实在判断时间戳哪里就已经可以过滤了,所以服务端代码修改如下

服务端:
//从参数中获取签名
String sign = sign;
//从签名中获取时间戳和约定的签名字符串
String[] strs = sign.split('-');
//获取签名的字符串
String singStr = strs.[0];
//判断签名字符串是否是约定好的
//获取时间戳
String time = strs[1];
//获取当前时间戳
long timeMillis = System.currentTimeMillis();
//获取随机数
String uuid = strs[2];
//判断二者的时间是否超出了限制
if(超出了) {
    return "请求超时";
}else{
  //判断该请求之前是否处理过了
  Boolean uuid = redisUtil.get(uuid);
  //如果存在,说明该请求之前处理过,就不做处理
  if(uuid) {
     return "该请求已经处理过了";
  }else{
     //如果不存在,可以正常处理
     //处理请求
     //将该随机数放到redis中
     redisUtil.set(uuid,uuid,5);  //设置过期时间为5分钟
  }
}


 

上一篇:键盘符号中英文名称


下一篇:Mac python 推送钉钉机器人