记录-java开发API接口防止重放攻击和参数防篡改

防止重放攻击

重放攻击(Replay Attacks)又称重播攻击、回放攻击,是指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。重放攻击在任何网络通过程中都可能发生,是计算机世界黑客常用的攻击方式之一。
例子:入侵者从网络上截取主机A发送给主机B的报文,并把由A加密的报文发送给B,使主机B误以为入侵者就是主机A,然后主机B向伪装成A的入侵者发送应当发送给A的报文。

常用的防止重放攻击策略主要分为以下两种:
1、基于 加时间戳的方案
2、基于 token 的方案
3、基于 时间戳和 token 的方案

基于时间戳的方案
在请求中增加时间戳参数要来表示请求时间戳,服务方端接收该请求后,根据当前时间生成一个接收时间戳,然后根据两个时间戳的差值进行请求判定,如果差值大于指定的阈值,则认为请求无效,否则请求通过。关于阈值的选定,可以根据接口的响应速度进行适当的调整,一般默认为 60 秒。

if((接收时间戳-请求时间戳) > 60){
"请求失败"
} else {
"请求通过"
}

该方案要求请求和响应的双方必须进行时间同步,如果服务的双方本身存在时间上的差异,会对差值的计算产生影响,最后导致请求的判定产生偏差。

缺点:通过上面的判定逻辑可以发现,在小于阈值的时间段内是可以进行重复请求的,该方案不能保证请求仅一次有效。

基于 token 的方案
在请求中增加一个通过指定规则产生的 token,标识请求的唯一性,服务方接收该请求后,先判断缓存集合中是否存在该 token,如果存在则认为此次请求无效,否则将 token 放入缓存中,通过请求通过。

if(token 存在于缓存集合中){
"请求失败"
} else {
将 token 放入集合中
"请求通过"
}

该方案要求 token 的生成规则要保证唯一性,如果 token 值重复,则会影响正常的请求访问。

缺点:token 存在于缓存中,而且没有有效期设置,随着请求量的增加,缓存集合中 token 的数量会非常庞大,会占用系统的大量内存。为了解决这个问题,我们引入了基于 时间戳和 token 的方案。

基于 时间戳和 token 的方案
时间戳解决 token 方案中缓存集合数据量大的问题,token 解决 时间戳方案中一次性访问的问题。伪代码如下:

if((接收时间戳-请求时间戳) > 60秒){
"请求失败"
} 
if(token 存在于缓存集合中){
"请求失败"
} else {
将 token 放入集合中,缓存时间60秒
"请求通过"
}

防篡改

在客户端与服务端请求交互的过程中,请求的数据容易被拦截并篡改,比如在支付场景中,请求支付金额为 10 元,被拦截后篡改为 100 元,由于没有防篡改校验,导致多支付了金钱,造成了用户损失。因此我们在接口设计时必须考虑防篡改校验,加签、验签就是用来解决这个问题的。划重点,敲黑板:加签、验签是用来解决防篡改问题的。

签名主要包含摘要和非对称加密两部分内容,首先对需要签名的数据进行摘要计算得到摘要值,然后通过签名者的私钥对摘要值进行非对称加密即可得到签名结果。

验签主要包含摘要、非对称解密、摘要比对三部分内容,首页对接收到的数据进行摘要计算得到验签方摘要值,然后通过签名者的公钥对摘要值进行非对称解密得到签名方摘要值,将签名方摘要值与验签方摘要值进行比对,如果相等则验签成功,否则验签失败。
签名
1、参数排序
将需要签名的内容根据参数名称进行排序,排序规则按照第一个字符的ASCII码值递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的ASCII码递增排序,以此类推。将参数内容进行排序,可以保证签名、验签双方参数内容的一致性。
2、参数拼接
将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待摘要字符串。

3、摘要计算
通过摘要算法求待摘要字符串的摘要值,常用的摘要算法如MD5、SHA、HMAC等。

4、非对称加密
使用非非对称加密算法,利用客户端的私钥对摘要值进行加密,生成内容我们称之为签名。

5、发送请求
将参数内容、字符编码、签名方法(非对称加密算法)、签名发送给验签方。

验签
验签方收到请求后进行验签。

1、参数排序
将收到参数内容根据参数名称进行排序,排序规则与签名方保持一致。

2、参数拼接
拼接方式与签名方保持一致,生成待摘要字符串。

3、摘要计算
使用相同的摘要算法计算得到验签方摘要值。

4、非对称解密
使用相同的非对称加密算法,对收到的签名进行解密,得到签名方摘要值。

5、摘要比对
如果签名方摘要值等于验签方摘要值,则验签成功,否则验签失败。
问题回顾
接口增加签名、验签后,在支付场景中,请求支付金额为 10 元,被拦截后篡改为 100 元,服务端接收到请求参数计算得到验签摘要值,通过非对称加密算法解密得到签名摘要值,由于金额发生了变化,验签摘要值不等于签名摘要值,因此验签失败,该请求不予处理。

关于加签、验签过程中使用到的算法、排序、拼接等都需要签名方开发人员、验签方开发人工共同协商与约定。BAT 开方平台提供的接口基本都采用的是这种方式进行签名的,在接口设计时可以参考他们的设计方案,大的平台经过千锤百炼,提炼出来的自然是精品,站在巨人的肩膀上可以让我们事半功倍。当然我们要做到知其然知其所以然。划重点,敲黑板:加签、验签是用来解决防篡改问题的。

API整体验证流程:

// 获取token
String token = request.getHeader("token");
// 获取时间戳
String timestamp = request.getHeader("timestamp");
// 获取随机字符串
String nonceStr = request.getHeader("nonceStr");
// 获取请求地址
String url = request.getHeader("url");
// 获取签名
String signature = request.getHeader("signature");

// 判断参数是否为空
if (StringUtils.isBlank(token) || StringUtils.isBlank(timestamp) || StringUtils.isBlank(nonceStr) || StringUtils.isBlank(url) || StringUtils.isBlank(signature)) {
    //非法请求
    return;
}

//验证token有效性,得到用户信息
UserTokenInfo userTokenInfo = TokenUtils.getUserTokenInfo(token);

if (userTokenInfo == null) {
    //token认证失败(防止token伪造)
    return;
}

// 判断请求的url参数是否正确
if (!request.getRequestURI().equals(url)){
    //非法请求 (防止跨域攻击)
    return;
}

// 判断时间是否大于60秒
if(DateUtils.getSecond()-DateUtils.toSecond(timestamp)>60){
    //请求超时(防止重放攻击)
    return;
}

// 判断该用户的nonceStr参数是否已经在redis中
if (RedisUtils.haveNonceStr(userTokenInfo,nonceStr)){
    //请求仅一次有效(防止短时间内的重放攻击)
    return;
}

// 对请求头参数进行签名
String stringB = SignUtil.signature(token, timestamp, nonceStr, url,request);

// 如果签名验证不通过
if (!signature.equals(stringB)) {
    //非法请求(防止请求参数被篡改)
    return;
}

// 将本次用户请求的nonceStr参数存到redis中设置60秒后自动删除
RedisUtils.saveNonceStr(userTokenInfo,nonceStr,60);

//开始处理合法的请求
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值