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

news2025/1/15 6:59:00

文章目录

      • 前言
      • 具体实现步骤
        • 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 接口恶意刷新和暴力请求?(荣耀典藏版)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue配置qiankun及打包上线

项目结构 基座&#xff1a;vue3 子应用A&#xff1a;vue3 子应用B&#xff1a; react 子应用C&#xff1a;vue3vite 项目目录&#xff1a; 配置基座 首先下载qiankun yarn add qiankun # 或者 npm i qiankun -S 所有子应用也要安装&#xff0c;vue-vite项目安装 cnpm ins…

XUbuntu22.04之快速复制绝对路径(二百零五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Java项目:115SSM宿舍管理系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 宿舍管理系统基于SpringSpringMVCMybatis开发&#xff0c;系统主要功能如下&#xff1a; 学生管理班级管理宿舍管理卫生管理维修登记访客管理 二、技术框…

向量数据库:Milvus

特性 Milvus由Go(63.4%),Python(17.0%),C(16.6%),Shell(1.3%)等语言开发开发&#xff0c;支持python&#xff0c;go&#xff0c;java接口(C,Rust,c#等语言还在开发中)&#xff0c;支持单机、集群部署&#xff0c;支持CPU、GPU运算。Milvus 中的所有搜索和查询操作都在内存中执行…

Phi-2小语言模型QLoRA微调教程

前言 就在不久前&#xff0c;微软正式发布了一个 27 亿参数的语言模型——Phi-2。这是一种文本到文本的人工智能程序&#xff0c;具有出色的推理和语言理解能力。同时&#xff0c;微软研究院也在官方 X 平台上声称&#xff1a;“Phi-2 的性能优于其他现有的小型语言模型&#…

C# WPF 数据绑定

需求 后台变量发生改变&#xff0c;前端对应的相关属性值也发生改变 实现 接口 INotifyPropertyChanged 用于通知客户端&#xff08;通常绑定客户端&#xff09;属性值已更改。 示例 示例一 官方示例代码如下 using System; using System.Collections.Generic; using Sy…

IoT 物联网 MQTT 协议 5.0 版本新特性

MQTT 是一种基于发布/订阅模式的轻量级消息传输协议&#xff0c;专门为设备资源有限和低带宽、高延迟的不稳定网络环境的物联网场景应用而设计&#xff0c;可以用极少的代码为联网设备提供实时可靠的消息服务。MQTT 协议广泛应用于智能硬件、智慧城市、智慧农业、智慧医疗、新零…

Linux:linux计算机和windows计算机 之间 共享资源

在前面章节已经介绍过&#xff0c;NFS用于Linux系统之间的文件共享&#xff0c;windows 并不知道 NFS &#xff0c;而是使用 CIFS (Common Internet File System) 的协议机制 来 “共享” 文件。在1991年&#xff0c;Andrew Tridgell 通过逆向工程 实现了 CIFS 协议&#xff0c…

GAMES101-Assignment5

一、问题总览 在这次作业中&#xff0c;要实现两个部分&#xff1a;光线的生成和光线与三角的相交。本次代码框架的工作流程为&#xff1a; 从main 函数开始。我们定义场景的参数&#xff0c;添加物体&#xff08;球体或三角形&#xff09;到场景中&#xff0c;并设置其材质&…

【Cadence】sprobe的使用

实验目的&#xff1a;通过sprobe测试电路中某个节点的阻抗 这里通过sprobe测试输入阻抗&#xff0c;可以通过port来验证 设置如下&#xff1a; 说明&#xff1a;Z1代表sprobe往left看&#xff0c;Z2代表sprobe往right看 结果如下&#xff1a; 可以看到ZM1I0.Z2 顺便给出了I…

一篇文章了解做仿真软件的达索系统-达索代理商

达索系统是一家全球领先的仿真软件公司&#xff0c;致力于为客户提供创新和高效的解决方案。该公司的仿真软件被广泛应用于航空航天、汽车、能源、医疗等领域&#xff0c;为客户提供了强大的工程仿真能力。 达索系统的仿真软件具有多个特点&#xff0c;包括高精度、高效率、易用…

CSS 改变鼠标样式(大全)

使用方法&#xff1a; <span style"cursor:auto">Auto</span><span style"cursor:crosshair">Crosshair</span><span style"cursor:default">Default</span><span style"cursor:pointer">P…

高通平台开发系列讲解(USB篇)adb function代码分析

文章目录 一、FFS相关动态打印二、代码入口三、ffs_alloc_inst四、ep0、ep1&ep2的注册五、读写过程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本文主要介绍高通平台USB adb function代码f_fs.c。 一、FFS相关动态打印 目录:msm-4.14/drivers/usb/gadget/fun…

系统存储架构升级分享

一、业务背景 系统业务功能&#xff1a;系统内部进行数据处理及整合, 对外部系统提供结果数据的初始化(写)及查询数据结果服务。 系统网络架构: • 部署架构对切量上线的影响 - 内部管理系统上线对其他系统的读业务无影响 •分布式缓存可进行单独扩容, 与存储及查询功能升级…

蓝牙信标定位原理

定位原理&#xff1a;蓝牙信标的定位原理是基于RSSI蓝牙信号强度来做定位的。 根据应用场景不同&#xff0c;通过RSSI定位原理可分为两种定位方式 一、存在性定位 这种方式通常要求所需定位的区域安装一个蓝牙信标即可&#xff0c;手持终端扫描蓝牙信标信号&#xff0c;扫描…

U盘删除的文件不在回收站如何恢复?教你3个简单方法!

“我在清理u盘的时候误删了一些重要的文件&#xff0c;想将这些文件恢复时才发现它们不在回收站中了。还有办法恢复吗&#xff1f;” 在数字化时代&#xff0c;u盘的作用渐渐显现。很多用户会将重要的数据直接保存在u盘中。但在使用u盘的过程中&#xff0c;不可避免会有数据的丢…

渐变登录页

效果演示 实现了一个简单的登录页面的样式和交互效果。 Code <div class"flex"><div class"login color">Login</div><label class"color">Username :</label><input type"text" class"input&…

第二证券:如何判断主力是在洗盘还是出货?

怎样判别主力是在洗盘仍是出货&#xff1f; 1、依据股票成交量判别 在洗盘时&#xff0c;个股的成交量与前几个生意相比较&#xff0c;呈现缩量的状况&#xff0c;而出货其成交量与前几个生意日相比较呈现放量的走势。 2、依据股票筹码分布判别 洗盘首要是将一些散户起浮筹…

如何创建自己的小程序?零编程一键创建实战指南

当今瞬息万变的数字世界中&#xff0c;拥有一个属于自己的小程序已成为企业与个人展示、服务和互动的重要途径。无需编码知识&#xff0c;通过便捷的云端可视化平台&#xff0c;也可以轻松创建一款符合自身需求且功能丰富的小程序。下面给大家分享如何创建自己的小程序。 1、选…

K8S Secret 一文详解, 全面覆盖 Secret 使用场景 | 全家桶

博客原文 文章目录 Secret介绍Secret 类型kubectl 创建类型 Secret 使用Opaque 类型 Secret 的使用创建1. kubectl create2. yaml 挂载1. 作为环境变量2. 作为文件挂载及设置 POSIX 权限 Secret 绑定 serviceAccount查看 secret TLS Secretyaml 方式创建kubectl 创建 Docker 镜…