内容简介:IP防刷,也就是在短时间内有大量相同ip的请求,可能是恶意的,也可能是超出业务范围的。总之,我们需要杜绝短时间内大量请求的问题,怎么处理?其实这个问题,真的是太常见和太简单了,但是真正来做的时候,可能就不一定很简单了哦。我这里给一个解决方案,以供参考!
IP防刷,也就是在短时间内有大量相同ip的请求,可能是恶意的,也可能是超出业务范围的。总之,我们需要杜绝短时间内大量请求的问题,怎么处理?
其实这个问题,真的是太常见和太简单了,但是真正来做的时候,可能就不一定很简单了哦。
我这里给一个解决方案,以供参考!
主要思路或者需要考虑的问题为:
1. 因为现在的服务器环境几乎都是分布式环境,所以,用本地计数的方式肯定是不行了,所以我们需要一个第三方的 工具 来辅助计数;
2. 可以选用数据库、缓存中间件、zk等组件来解决分布式计数问题;
3. 使用自增计数,尽量保持原子性,避免误差;
4. 统计周期为从当前倒推 interval 时间,还是直接以某个开始时间计数;
5. 在何处进行拦截? 每个方法开始前? 还是请求入口处?
实现代码示例如下:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import redis.clients.jedis.Jedis; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; /** * IP 防刷工具类, 10分钟内只最多允许1000次用户操作 */ @Aspect public class IpFlushFirewall { @Resource private Jedis redisTemplate; /** * 最大ip限制次数 */ private static int maxLimitIpHit = 1000; /** * 检查时效,单位:秒 */ private static int checkLimitIpHitInterval = 600; // 自测试有效性 public static void main(String[] args) { IpFlushFirewall ipTest = new IpFlushFirewall(); // 测试时直接使用new Jedis(), 正式运行时使用 redis-data 组件配置即可 ipTest.redisTemplate = new Jedis("127.0.0.1", 6379); for (int i = 0; i < 10; i++) { System.out.println("new action: +" + i); ipTest.testLoginAction(new Object()); System.out.println("action: +" + i + ", passed..."); } } // 测试访问的方法 public Object testLoginAction(Object req) { // ip防刷 String reqIp = "127.0.0.1"; checkIpLimit(reqIp); // 用户信息校验 System.out.println("login success..."); // 返回用户信息 return null; } // 检测限制入口 public void checkIpLimit(String ip) { if(isIpLimited(ip)) { throw new RuntimeException("操作频繁,请稍后再试!"); } } // ip 防刷 / 使用切面进行拦截 @Before(value = "execution(public * com.*.*.*(..))") public void checkIpLimit() { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); String ip = getIp(request); if(isIpLimited(ip)) { throw new RuntimeException("操作频繁,请稍后再试!"); } } public static String getIp(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } // 多级代理问题 if(ip.contains(",")) { ip = ip.substring(0, ip.indexOf(',')).trim(); } return ip; } /** * 判断ip是否受限制, 非核心场景,对于非原子的更新计数问题不大,否则考虑使用分布式锁调用更新 */ private boolean isIpLimited(String reqIp) { String ipHitCache = getIpHitCacheKey(reqIp); // 先取旧数据作为本次判断,再记录本次访问 String hitsStr = redisTemplate.get(ipHitCache); recordNewIpRequest(reqIp); // 新周期内,首次访问 if(hitsStr == null) { return false; } // 之前有命中 // 总数未超限,直接通过 if(!isOverMaxLimit(Integer.valueOf(hitsStr) + 1)) { return false; } // 当前访问后超过限制后,再判断周期内的数据 Long retainIpHits = countEffectiveIntervalIpHit(reqIp); redisTemplate.set(ipHitCache, retainIpHits + ""); // 将有效计数更新回计数器,删除无效计数后,在限制范围内,则不限制操作 if(!isOverMaxLimit(retainIpHits.intValue())) { return false; } return true; } // 是否超过最大限制 private boolean isOverMaxLimit(Integer nowCount) { return nowCount > maxLimitIpHit; } // 每次访问必须记录 private void recordNewIpRequest(String reqIp) { if(redisTemplate.exists(getIpHitCacheKey(reqIp))) { // 自增访问量 redisTemplate.incr(getIpHitCacheKey(reqIp)); } else { redisTemplate.set(getIpHitCacheKey(reqIp), "1"); } redisTemplate.expire(getIpHitCacheKey(reqIp), checkLimitIpHitInterval); Long nowTime = System.currentTimeMillis() / 1000; // 使用 sorted set 保存记录时间,方便删除, zset 元素尽可能保持唯一,否则 redisTemplate.zadd(getIpHitStartTimeCacheKey(reqIp), nowTime , reqIp + "-" + System.nanoTime() + Math.random()); redisTemplate.expire(getIpHitStartTimeCacheKey(reqIp), checkLimitIpHitInterval); } /** * 统计计数周期内有效的的访问次数(删除无效统计) * * @param reqIp 请求ip * @return 有效计数 */ private Long countEffectiveIntervalIpHit(String reqIp) { // 删除统计周期外的计数 Long nowTime = System.currentTimeMillis() / 1000; redisTemplate.zremrangeByScore(getIpHitStartTimeCacheKey(reqIp), nowTime - checkLimitIpHitInterval, nowTime); return redisTemplate.zcard(getIpHitStartTimeCacheKey(reqIp)); } // ip 访问计数器缓存key private String getIpHitCacheKey(String reqIp) { return "secure.ip.limit." + reqIp; } // ip 访问开始时间缓存key private String getIpHitStartTimeCacheKey(String reqIp) { return "secure.ip.limit." + reqIp + ".starttime"; } }
如上解决思路为:
1. 使用 redis 做计数器工具,做到数据统一的同时,redis 的高性能特性也保证了整个应用性能;
2. 使用 redis 的 incr 做自增,使用一个 zset 来保存记录开始时间;
3. 在计数超过限制后,再做开始有效性的检测,保证准确的同时,避免了每次都手动检查有时间有效性的动作;
4. 使用切面的方式进行请求拦截,避免代码入侵;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- ASP中解决“对象关闭时,不允许操作。”的诡异问题……
- Python 新功能:或将允许安全工具查看运行时操作
- c – 强制允许取消引用NULL指针
- xml – 如何定义XSD以允许任何元素
- html – 如果需要,我如何允许文字包装一个字?
- Microsoft Teams 升级机制允许黑客执行任意文件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机图形学原理及实践:C语言描述(原书第2版) (平装)
福利 / 唐泽圣 / 机械工业出版社 / 2004-3 / 95.0
《计算机图形学原理及实践:C语言描述(原书第2版)》:这是计算机图形学领域的一部经典之作,作者Fley、va Dam等是国际图形学界的著名学者、学术带头人,而且《计算机图形学原理及实践:C语言描述(原书第2版)》英文版自出版以来,一直是各国大学计算机图形学课程的主要教科书。来自清华大学、北京大学、中国科学院计算技术研究所、中国科学院软件研究所的多位图形学领域的专家和精英花费了大量的时间和精力进行翻......一起来看看 《计算机图形学原理及实践:C语言描述(原书第2版) (平装)》 这本书的介绍吧!
html转js在线工具
html转js在线工具
RGB HSV 转换
RGB HSV 互转工具