Java--业务场景:SpringBoot 通过Redis进行IP封禁实现接口防刷

文章目录

      • 前言
      • 具体实现步骤
        • 1. 定义自定义注解
        • 2. 编写拦截器类IpUrlLimitInterceptor
        • 3. 在WebConfig类中添加IpUrlLimitInterceptor
        • 4. 添加注解到接口上
      • 测试效果
      • 参考文章

前言

  • 在实际项目中,有些攻击者会使用自动化工具来频繁刷新接口,造成系统的瞬时吞吐量提高,给系统带来很大的压力。要保障服务的安全性,需要防止重要的接口被恶意刷新,接口防刷的方式可以通过设置验证码,IP封禁,安全参数校验等方法。
  • 本文主要采用Redis将同一时间内频繁访问同一接口的IP封禁一段时间的方式来防止接口被恶意刷新。

具体实现步骤

1. 定义自定义注解
  • 添加了该注解的接口,将开启接口防刷功能。
    /**
    * 防刷注解
    */
    @Target(ElementType.METHOD)
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AccessLimit {/*** 表示规定的时间范围*/int seconds();/*** 表示在规定的时间范围内最多可被访问的次数*/int maxCount();/*** 表示该接口是否需要登录,默认为true*/boolean needLogin() default true;
    }
    
2. 编写拦截器类IpUrlLimitInterceptor
  • 核心拦截器IpUrlLimitInterceptor的代码如下:
     @Slf4jpublic class IpUrlLimitInterceptor implements HandlerInterceptor {@AutowiredRedisUtil redisUtil; //redis工具类@Autowiredprivate TokenManager tokenManager; //登录时的token检验管理器private static final String LOCK_IP_URL_KEY = "lock_ip_";private static final String IP_URL_REQ_TIME = "ip_url_times_";private static final int IP_LOCK_TIME = 60; //IP被禁用的时间 此处为了方便测试,设置为一分钟 实际情况应该在配置文件里设置@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {if (o instanceof HandlerMethod) {HandlerMethod hm = (HandlerMethod) o;// 获取AccessLimit注解AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);if(Objects.isNull(accessLimit)){return true;}log.info("request请求地址uri={},ip={}", httpServletRequest.getRequestURI(), IpUtil.getIp(httpServletRequest));//判断IP是否被锁定,若被锁定则访问异常提示信息if (ipIsLock(IpUtil.getIp(httpServletRequest))) {log.info("ip访问被禁止={}", IpUtil.getIp(httpServletRequest));Result result = Result.exception().code(ResultCode.LOCK_IP).message("该IP已被锁定,请等候解锁");returnJson(httpServletResponse, JSON.toJSONString(result));return false;}//接口若需要登录,则校验token//获取请求头里的token信息判断是否正确,若token不正确,则return falseif(accessLimit.needLogin()&&!tokenManager.checkToken(httpServletRequest.getHeader("Authorization"))){ return false;}//记录请求次数,记录后若大于规定时间内的规定次数则返回异常提示信息if (!addRequestTime(IpUtil.getIp(httpServletRequest), httpServletRequest.getRequestURI(), accessLimit.seconds(),accessLimit.maxCount())) {Result result = Result.exception().code(ResultCode.LOCK_IP).message("该IP已被锁定,请等候解锁");returnJson(httpServletResponse, JSON.toJSONString(result));return false;}}return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {}/*** @param ip* @return java.lang.Boolean* @Description: 判断ip是否被禁用*/private Boolean ipIsLock(String ip) {if (redisUtil.hasKey(LOCK_IP_URL_KEY + ip)) {return true;}return false;}/*** @param ip* @param uri* @return java.lang.Boolean* @Description: 记录请求次数*/private Boolean addRequestTime(String ip, String uri,int seconds,int maxCount) {String key = IP_URL_REQ_TIME + ip + uri;if (redisUtil.hasKey(key)) {//访问次数加1long time = redisUtil.incrBy(key, 1);if (time >= maxCount) {redisUtil.getLock(LOCK_IP_URL_KEY + ip, ip, IP_LOCK_TIME);return false;}} else {//seconds秒内访问maxCount次就锁柱redisUtil.getLock(key, 1, seconds);}return true;}private void returnJson(HttpServletResponse response, String json) throws Exception {PrintWriter writer = null;response.setCharacterEncoding("UTF-8");response.setContentType("text/json; charset=utf-8");try {writer = response.getWriter();writer.print(json);} catch (IOException e) {log.error("LoginInterceptor response error ---> {}", e.getMessage(), e);} finally {if (writer != null) {writer.close();}}}}
    
  • 上述代码中的RedisUtil具体方法如下,完整的RedisUtil类获取方式:Java - Redis操作的工具类RedisUtil
    @Component
    @Slf4j
    public class RedisUtil {private static final Long SUCCESS = 1L;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 获取锁* 代码中redis的使用的是分布式锁的形式,这样可以最大程度保证线程安全和功能的实现效果。* @param lockKey* @param value* @param expireTime:单位-秒* @return*/public boolean getLock(String lockKey, Object value, int expireTime) {try {log.info("添加分布式锁key={},expireTime={}", lockKey, expireTime);String script = "if redis.call('setnx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime);if (SUCCESS.equals(result)) {return true;}} catch (Exception e) {e.printStackTrace();}return false;}//其他方法....
    }
    
  • 拦截器中的IpUtil工具类获取方式:Java-IpUtil通过请求获取IP信息的工具类
3. 在WebConfig类中添加IpUrlLimitInterceptor
  @Configurationpublic class WebConfig extends WebMvcConfigurerAdapter  {@BeanIpUrlLimitInterceptor getIpUrlLimitInterceptor() {return new IpUrlLimitInterceptor();}/*** 注册登录ip防刷拦截器* @return*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getIpUrlLimitInterceptor()).addPathPatterns("/**");super.addInterceptors(registry);}
}
4. 添加注解到接口上
  • 编写一个接口,将刚刚的防刷注解添加上去
  @RestController@RequestMapping("/part/util")public class UtilController {/*** 防刷注解测试* @return*/@GetMapping("/ipLimitTest")@AccessLimit(seconds = 1,maxCount = 5,needLogin = false)//表示一秒内该接口只能访问五次,防止恶意刷流量,这里接口无需登录public Result ipLimitTest(){return Result.ok().data("访问成功");} }

测试效果

  • 手写一个for循环请求10次ipLimitTest()接口,观察日志情况如下:
    在这里插入图片描述

  • 超过五次之后,该ip就被锁定1分钟。一分钟内的访问被禁止。此时查询redis的key,可以发现该ip锁住。
    在这里插入图片描述

参考文章

如何解决SpringBoot 接口恶意刷新和暴力请求?(荣耀典藏版)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/335956.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上海亚商投顾:三大指数小幅反弹,旅游、机器人板块集体走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日震荡反弹&#xff0c;创业板指一度涨超1.7%&#xff0c;午后集体回落翻绿&#xff0c;临近尾盘又…

免费分享一套微信小程序扫码点餐(订餐)系统(uni-app+SpringBoot后端+Vue管理端技术实现) ,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序扫码点餐(订餐)系统(uni-appSpringBoot后端Vue管理端技术实现) &#xff0c;分享下哈。 项目视频演示 【免费】微信小程序扫码点餐(订餐)系统(uni-appSpringBoot后端Vue管理端技术实现) Java毕…

【elfboard linux开发板】11. 版本管理和修改设备树流程(点亮LED)

1. 版本管理 1.1 初始化git仓库 git init 生成一个.git 目录 git config --global user.name 用户名 git config --global user.email 邮箱 1.2 查看.gitignore vim .gitignore 1.3 添加删除到缓存区 git status 查看状态 git add 文件名 git rm 文件名 1.4 提交当前记录 …

【复习】人工智能 第7章 专家系统与机器学习

专家系统就是让机器人当某个领域的专家&#xff0c;但这章专家系统不咋考&#xff0c;主要靠书上没有的机器学习。 一、专家系统的基本组成 二、专家系统与传统程序的比较 &#xff08;1&#xff09;编程思想&#xff1a; 传统程序 数据结构 算法 专家系统 知识 推理 &…

关于java的冒泡排序

关于java的冒泡排序 我们前面的文章中了解到了数组的方法类Arrays&#xff0c;我们本篇文章来了解一下最出名的排序算法之一&#xff0c;冒泡排序&#xff01;&#x1f600; 冒泡排序的代码还是非常简单的&#xff0c;两层循环&#xff0c;外层冒泡轮数&#xff0c;里层依次比…

日志系统一(elasticsearch+filebeat+logstash+kibana)

目录 一、es集群部署 安装java环境 部署es集群 安装IK分词器插件 二、filebeat安装&#xff08;docker方式&#xff09; 三、logstash部署 四、kibana部署 背景&#xff1a;因业务需求需要将nginx、java、ingress日志进行收集。 架构&#xff1a;filebeatlogstasheskib…

TypeScript基础(五)泛型

✨ 专栏介绍 TypeScript是一种由微软开发的开源编程语言&#xff0c;它是JavaScript的超集&#xff0c;意味着任何有效的JavaScript代码都是有效的TypeScript代码。TypeScript通过添加静态类型和其他特性来增强JavaScript&#xff0c;使其更适合大型项目和团队开发。 在TypeS…

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析 【赛程名称】高职组-云计算赛项第一场-私有云【任务 1】私有云服务搭建[10 分]【题目 2】Yum 源配置[0.5 分]【题目 3】配置无秘钥 ssh[0.5 分]【题目 4】基础安装[0.5 分]【题目 5】数据库安装与调优[0.5 分]【题目 …

再谈前端算法

楔子 – 青蛙跳台阶什么是算法算法实例 &#xff1a; 实现一个LRU缓存 实现 LRUCache扩展&#xff1a; ES6 Map Map的创建和初始化&#xff1a;添加键值对&#xff1a;获取键值对&#xff1a;检查Map中是否存在某个键&#xff1a;删除键值对&#xff1a;遍历Map&#xff1a;获取…

Python综合数据分析_RFM用户分组模型

文章目录 1.导入数据2.月度订单数据可视化3.数据清洗4.特征工程5.构建User用户表6.求R值7.求F值8.求M值9.显示R、F、M值的分布情况10.显示手肘图辅助确定K值11.创建和训练模型12.给R值聚类13.给聚类后的层级排序14.继续给F、M值聚类&#xff0c;并排序15.为用户整体分组画像 1.…

数据库连接池配置生成、读取二维码集成多数据源入参字段定义为Date类型,支持时间戳及年-月-日

数据库连接池配置 # 数据源配置 spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverdruid:# 主库数据源master:url: jdbc:mysql://localhost:3306/database?useUnicodetrue&characterEncodingutf8&zeroDateT…

数据库:如何取消mysql的密码

因为调试MySQL数据接口&#xff0c;总是需要输入密码很烦&#xff0c;所以决定取消mysql的root密码&#xff0c; 网上推荐的有两种方法&#xff1a; 1、mysql命令 SET PASSWORD FOR rootlocalhostPASSWORD(); 2、运行 mysqladmin 命令 mysqladmin -u root -p password …