一、写在前面
在互联网的发展史上,安全总是一个绕不开话题, 你有安全盾、我有破盾矛。所谓道高一尺、魔高一丈,不过互联网安全也正是在这种攻防中慢慢的发展起来的。
不过今天写的没有上面说的那么高大,只是一个小小的防刷解决思路。
这是工作中经常遇到的、在此仅做一个记录,以便回顾。
如有不严谨或者不完善的地方,欢迎指正 ~谢谢~
二、场景引入、问题凸显
场景: 在我们给 xx 做的区块链共享出行平台中区块链浏览器系统的登录是以用户的手机号+验证码来登录的。
因是内部用户使用、在登录这块也没做特殊的安全处理,导致在测试时被我们的测试小哥给刷爆了(在这给测试小哥点个赞)。
到这我们必然的收到一个bug了,业务期望。
1: 同一个ip限制一分钟最多获取5次
2: 超过5次则锁定1小时,锁定期间获取短信需加图片验证码
收到这个需求、利用Redis做了简单的限流防刷功能。
三、解决方案
首先分析需求,
1: 对同一IP做限制
2: 对单位时间内次数做限制
利用Redis来实现思路
1: 一个获取短信验证码请求过来我们首先的判断此IP是否已经被锁定(单位时间内超过了限定的访问次数)
2: 如果未被锁定则判断此IP是否是首次访问,如果是则给此IP加个生命周期以及记录访问次数。
3: 如果不是首次访问,则判断单位时间内是否符合限制要求。
完成这三不我们需要在 redis 中定义三个KEY
msg_lock_key_{ip}
记录次IP已被锁定
msg_time_key_{ip}
记录单位时间内IP
msg_counter_key_{ip}
记录单位时间内IP访问次数
到这就直接上一段代码吧,
public boolean checkMsgFrequency(String remotIp) {
String romteIpString = remotIp.replace(".", "");
// 1: 判断此ip是否已经被限制
String msgLockKey = Constants.API_MSG_LOCK_KEY + romteIpString;
if (jedis.exists(msgLockKey)) {
// 此ip已经被锁住
logger.info("此Ip[{}]以超过规定访问频率key:{}", remotIp, msgLockKey);
return false;
}
// 2: 判断此ip是否在规定的时间内访问过
String msgTimeKey = Constants.API_MSG_TIME_KEY + romteIpString;
String msgCounterKey = Constants.API_MSG_COUNTER_KEY + romteIpString;
// key 不存在
if (!jedis.exists(msgTimeKey)) {
// 加入缓存
jedis.setEx(msgTimeKey, "0", imageCode.getLimitTime());
jedis.setEx(msgCounterKey, "1", imageCode.getLimitTime());
}
if (jedis.exists(msgTimeKey) && (jedis.incrBy(msgCounterKey, 1) > imageCode.getLimitCounter())) {
logger.info("此Ip[{}]以超过规定访问频率、进行枷锁key:{}", remotIp, msgTimeKey);
jedis.setEx(msgLockKey, "0", imageCode.getLimitlockTime());
return false;
}
return true;
}
这是一个简单的代码实现, 逻辑就是这个逻辑,实现方式很多种。
你可以使用spring AOP + 自定义注解对逻辑进行分装。
也可以使用Redis + lua脚本对上述三步逻辑进行分装。
这个就看个人,重要的的理解实现的思路, 实现方式千万种总有一种是适合你的。
四、总结
对稀有资源的限流防刷,一般对单位时间的访问频率或者次数的限制。
我们主要是理解其解决问题的思路, 而不是记住实现代码。
主要思路:
1: 查看针对次请求(IP 或者 uid) 对当前请求资源是否已被锁定。
2: 如果没有被锁定,则给此请求加生命周期(也就是一个限制时间单位),同时记录访问次数。
3: 则判断此请求是否达到锁定条件
大致思想就是这三步。
此记录为加深自己记忆,同时以便温故,若能帮到你,万分高兴。
如有不对的地方欢迎留言指正 谢谢!
如您有更好的思路欢迎交流