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的代码如下:
     @Slf4j
     public class IpUrlLimitInterceptor implements HandlerInterceptor {
    
        @Autowired
        RedisUtil redisUtil; //redis工具类
        @Autowired
        private 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被禁用的时间 此处为了方便测试,设置为一分钟 实际情况应该在配置文件里设置
    
        @Override
        public 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 false
             if(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;
        }
    
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}
    
        @Override
        public 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)) {
            //访问次数加1
            long 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;
    
      @Autowired
      private 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
  @Configuration
  public class WebConfig extends WebMvcConfigurerAdapter  {
    @Bean
    IpUrlLimitInterceptor getIpUrlLimitInterceptor() {
        return new IpUrlLimitInterceptor();
    }

    /**
     * 注册登录ip防刷拦截器
     * @return
     */
    @Override
    public 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 接口恶意刷新和暴力请求?(荣耀典藏版)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一些前端学习过程的自测练习题

目录 页面设计部分 1 设计一个简单的学院网站首页&#xff1b; 2.按照图示要求完成简单的登录页面 3.完成如下网站设计 4.完成如下网站设计&#xff08;练习页面布局&#xff09; 5 利用下面素材&#xff0c;设计一个满足H5规范的网页&#xff08;移动端页面练习&#xff…

leetcode刷题记录18(2023-08-29)【最短无序连续子数组(单调栈) | 合并二叉树(dfs) | 任务调度器(桶) | 回文子串(二维dp)】

581. 最短无序连续子数组 给你一个整数数组 nums &#xff0c;你需要找出一个 连续子数组 &#xff0c;如果对这个子数组进行升序排序&#xff0c;那么整个数组都会变为升序排序。 请你找出符合题意的 最短 子数组&#xff0c;并输出它的长度。 示例 1&#xff1a; 输入&am…

TensorRT模型优化模型部署(七)--Quantization量化(PTQ and QAT)(二)

系列文章目录 第一章 TensorRT优化部署&#xff08;一&#xff09;–TensorRT和ONNX基础 第二章 TensorRT优化部署&#xff08;二&#xff09;–剖析ONNX架构 第三章 TensorRT优化部署&#xff08;三&#xff09;–ONNX注册算子 第四章 TensorRT模型优化部署&#xff08;四&am…

Java中finally和return的执行顺序

Java中finally和return的执行顺序 try...catch...finally1. finally语句在return语句执行之后return返回之前执行的2. finally块中的return语句会覆盖try块中的return返回3. 如果finally语句中没有return语句覆盖返回值&#xff0c;那么原来的返回值可能因为finally里的修改而改…

进程的状态

进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。在三态模型 中&#xff0c;进程状态分为三个基本状态&#xff0c;即就绪态&#xff0c;运行态&#xff0c;阻塞态。在五态模型中&#xff0c;进程分为新建态、就绪态&#xff0c;运行态&#x…

【书生·浦语】大模型实战营——第四课笔记

教程链接&#xff1a;https://github.com/InternLM/tutorial/blob/main/xtuner/README.md 视频链接&#xff1a;https://www.bilibili.com/video/BV1yK4y1B75J/?vd_source5d94ee72ede352cb2dfc19e4694f7622 本次视频的内容分为以下四部分&#xff1a; 目录 微调简介 微调会使…

【ArcGIS遇上Python】ArcGIS Python批量筛选多个shp中指定字段值的图斑(以土地利用数据为例)

文章目录 一、案例分析二、提取效果二、代码运行效果三、Python代码四、数据及代码下载一、案例分析 以土地利用数据为例,提取多个shp数据中的旱地。 二、提取效果 原始土地利用数据: 属性表: 提取的旱地:(以图层名称+地类名称命名)

数据结构——排序算法之快速排序

个人主页&#xff1a;日刷百题 系列专栏&#xff1a;〖C/C小游戏〗〖Linux〗〖数据结构〗 〖C语言〗 &#x1f30e;欢迎各位→点赞&#x1f44d;收藏⭐️留言&#x1f4dd; ​ ​ 前言&#xff1a; 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。 基本思想&…

弟12章 1 网络编程

文章目录 网络协议概述 p164TCP协议与UDP协议的区别 p165 网络协议概述 p164 ipv4&#xff1a;十进制点分制 ipv6&#xff1a;十六进制冒号分隔 TCP协议与UDP协议的区别 p165 tcp协议的三次握手&#xff1a;

MySQL单表查询

显示所有职工的基本信息。 mysql8.0 [chap03]>select * from worker; 查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 mysql8.0 [chap03]>select distinct(部门号) from worker; 求出所有职工的人数。 mysql8.0 [chap03]>select count(*) from …

山西电力市场日前价格预测【2024-01-14】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-01-14&#xff09;山西电力市场全天平均日前电价为415.13元/MWh。其中&#xff0c;最高日前电价为851.84元/MWh&#xff0c;预计出现在18:15。最低日前电价为198.87元/MWh&#xff0c;预计…

04.neuvector进程策略生成与管控实现

原文链接&#xff0c;欢迎大家关注我的github 一、进程学习管控的实现方式 策略学习实现&#xff1a; 进程的学习与告警主要依据通过netlink socket实时获取进程启动和退出的事件: 1.创建netLink socket&#xff1b; 2.通过创建netlink的fd对进程的事件进行捕获与更新&#x…

“超人练习法”系列08:ZPD 理论

01 先认识一个靓仔 看过 Lev Vygotsky 这个人的书吗&#xff1f;他是一位熟练心理学家&#xff0c;对人们习得技能的方式非常感兴趣&#xff0c;但他 37 岁的时候就因肺炎英年早逝了。 他认为社会环境对学习有关键性的作用&#xff0c;认为社会因素与个人因素的整合促成了学习…

计算机网络 —— 数据链路层

数据链路层 3.1 数据链路层概述 数据链路层把网络层交下来的数据构成帧发送到链路上&#xff0c;以及把收到的帧数据取出并上交给网络层。链路层属于计算机网络的底层。数据链路层使用的信道主要由以下两种类型&#xff1a; 点对点通信。广播通信。 数据链路和帧 链路&…

UniRepLKNet实战:使用 UniRepLKNet实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

LV.13 D10 Linux内核移植 学习笔记

一、Linux内核概述 1.1 内核与操作系统 内核 内核是一个操作系统的核心&#xff0c;提供了操作系统最基本的功能&#xff0c;是操作系统工作的基础&#xff0c;决定着整个系统的性能和稳定性 操作系统 操作系统是在内核的基础上添加了各种工具集、桌面管理器、库、…

基于Java SSM框架实现企业车辆管理系统项目【项目源码】计算机毕业设计

基于java的SSM框架实现企业车辆管理系统演示 JSP技术 JSP技术本身是一种脚本语言&#xff0c;但它的功能是十分强大的&#xff0c;因为它可以使用所有的JAVA类。当它与JavaBeans 类进行结合时&#xff0c;它可以使显示逻辑和内容分开&#xff0c;这就极大的方便了运动员的需求…

关于html导出word总结一

总结 测试结果不理想&#xff0c;html-to-docx 和 html-docx-js 最终导出的结果 都 差强人意&#xff0c;效果可以见末尾的附图 环境 "electron": "24.3.0" 依赖库 html-docx-js html-docx-js - npm html-to-docx html-to-docx - npm file-saver…

如何将重复方法封装为Aop切面并结合注解使用

首先要导入依赖 <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId> </dependency> 编写注解 package com.yg.domain.note;import java.lang.annotation.ElementType; import java.lang.annotation.Rete…

PyCharm连接服务器 - 2

文章目录 PyCharm连接服务器-21.如何连接服务器&#xff1f;2.如何在终端窗口打开SSH连接&#xff1f;3.Terminal终端出现中文乱码的解决办法&#xff1f;4.如何查看远程服务器的树目录结构&#xff1f;5.如何配置代码同步&#xff1f;6.如何为项目配置远程服务器中的python解释…