说明:目前所有的系统架构都采用前后分离的系统架构,不可避免的服务需要对外提供API。 那么,如何保证对外的API安全呢?
即,生鲜食品EC中API接口防止参数的篡改和重放攻击
目录
1 .什么是API参数篡改?
说明: API参数篡改是指恶意人通过抓取数据包获取所请求接口的参数,并修改相关参数,以达到欺骗服务器的目的,常见的防篡改方式是签名和加密方式。 关注公众号猴技术专栏获取更多面试资源
2 .什么是API重发攻击?
说明: API重放攻击:是将以前窃听的数据直接重新发送到接收方。
3、常用解决方案
常见的其他业务场景如下
发送短信界面
支付接口
基于timestamp和nonce的方案
微信支付的界面就是这样做的
timestamp角色
对于每个HTTP请求,必须添加timestamp参数,并对timestamp和其他参数进行数字签名。 由于HTTP请求从发出到到达服务器通常不超过60s,所以服务器在接收到HTTP请求后,首先判断时间戳参数与当前时间相比是否超过60s,如果超过,则判断为非法请求。
目前正在请求的timestamp参数已禁用,因为播放来自捕获的请求所需的时间通常远远超过60s。 如果将timestamp参数更改为当前时间戳,则与signature参数对应的数字签名将无效。 无法生成新的数字签名,因为您不知道签名私钥。
但是,这种方式的脆弱性也是显而易见的,60s以后进行重放攻击的话,没办法,所以用这种方式不能保证要求只有效一次
nonce的作用
nonce是指一个唯一有效的随机字符串,必须确保每个请求的此参数都不一样。 将每个请求的nonce参数存储在“集合”中,并在每次处理HTTP请求时,首先确定该请求的nonce参数是否位于该“集合”中,如果存在,则将其视为非法请求。
nonce参数在第一次请求时存储在服务器上的“集合”中,可以识别并拒绝重发请求。
nonce参数不能作为数字签名的一部分进行篡改。 无法生成新的数字签名,因为您不知道签名的私钥。
这种方式也有很大的问题。 那就是存储nonce参数的“集合”越来越大。
nonce的一次性使用可以解决timestamp参数60s (防止重放攻击)的问题,timestamp可以解决nonce参数“集合”越来越大的问题。
防篡改防重放攻击拦截器(用于redis ) ) ) ) ) ) ) ) )。
publicclassignauthinterceptorimplementshandlerinterceptor {
权限显示模板;
私有密钥;
publicsignauthinterceptor (redistemplateredistemplate,Stringkey ) )。
this.redis template=redis template;
this.key=key;
}
@Override
publicbooleanprehandle (httpservletrequestrequest,
HttpServletResponseresponse,objecthandler(Throwsexception )。
//获取时间戳
string timestamp=request.get header (timestamp );
//获取随机字符串
stringnoncestr=request.get header (' nonce str );
//得到签名
string signature=request.get header (signature );
//判断时间是否长于xx秒((防止重放攻击) ) ) ) )。
long nonce _ str _ time out _ seconds=60l;
str util.isempty (timestamp ) (|dateutil.between ) date util.date (long.parse long ) timestamp ) *1000 ),date
thrownewbusinessexception (invalidtimestamp );
}
//判断该用户的nonceStr参数是否已经进入redis (防止短时间的重放攻击) )。
booleanhavenoncestr=redis template.has key (nonce str );
str util.isempty (nonce str ) |
Objects.isNull(haveNonceStr) || haveNonceStr) {throw new BusinessException("invalid nonceStr");
}
// 对请求头参数进行签名
if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
throw new BusinessException("invalid signature");
}
// 将本次用户请求的nonceStr参数存到redis中设置xx秒后自动删除
redisTemplate.opsForValue().set(nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.SECONDS);
return true;
}
private String signature(String timestamp, String nonceStr, HttpServletRequest request) throws UnsupportedEncodingException{
Map params = new HashMap<>(16);
Enumeration enumeration = request.getParameterNames();
if (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getParameter(name);
params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));
}
String qs = String.format("%s×tamp=%s&nonceStr=%s&key=%s", this.sortQueryParamString(params), timestamp, nonceStr, key);
log.info("qs:{}", qs);
String sign = SecureUtil.md5(qs).toLowerCase();
log.info("sign:{}", sign);
return sign;
}
/**
* 按照字母顺序进行升序排序
*
* @param params 请求参数 。注意请求参数中不能包含key
* @return 排序后结果
*/
private String sortQueryParamString(Map params){
List listKeys = Lists.newArrayList(params.keySet());
Collections.sort(listKeys);
StrBuilder content = StrBuilder.create();
for (String param : listKeys) {
content.append(param).append("=").append(params.get(param).toString()).append("&");
}
if (content.length() > 0) {
return content.subString(0, content.length() - 1);
}
return content.toString();
}
}
总结:做互联网应用,无论是生鲜小程序还是APP,安全永远都是第一位,安全做好了,其他的才能做得更好,当然,信息安全是一个永久的话题,并非通过本文就能够说得清楚的
希望本文可以给大家一点思考与建议。