使用redis制作一个简单的防御模块 抵御恶意http请求攻击
前言
本人是个菜鸡大学仔,平时也没有写csdn的习惯,刚刚做完一个小型外包项目不久没什么事,突然心血来潮,突发大胆想法。多年来由于始终不敢相信2G+1M的阿里云服务器的性能,一直想要自己尝试做一个简单的HTTP安全模块,防止好事者通过浏览器或者程序使用http访问刷我的服务器,恰逢今日有空,打算在原本的一个自己开发的二手市场springboot项目上使用刚学不久的redis进行恶意http攻击检测.,现在在csdn上做这光荣一刻的伟大记录,我习惯于把代码当做文章口述的一部分,而不会大规模复制代码,所以springboot基础薄弱的看着可能会有一些不适. 请见谅
提示:以下是本篇文章正文内容,下面案例可供参考
一、原理和思路
由于redis是基于内存的缓存服务器,性能高,稳定性也经住了市场的考验,自定义拦截器,连接请求,使用redis来记录每个http请求ip的近期访问次数,w如果发现了异常情况(短时间内发送了大量请求)则可以认为是恶意的HTTP请求攻击.可以对该IP进行封锁处理.并且可以记录该IP到日志上.
二、程序步骤
1.引入依赖和配置redis
为了方便测试,我使用工作电脑调试和运行springboot项目,而redis服务器搭建在阿里云上.
  
   - 
    
     
    
    
     
      <!--redis 依赖-->
     
    
- 
    
     
    
    
     
      <dependency>
     
    
- 
    
     
    
    
     	
      <groupId>org.springframework.boot
      </groupId>
     
    
- 
    
     
    
    
     	
      <artifactId>spring-boot-starter-data-redis
      </artifactId>
     
    
- 
    
     
    
    
     
      </dependency>
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      #redis配置
     
    
- 
    
     
    
    
     
      spring.redis.host = ...
     
    
- 
    
     
    
    
     
      spring.redis.port = ... [默认6379]
     
    
- 
    
     
    
    
     
      spring.redis.password = [如果你的redis服务器有密码]
     
    
 2.自定义redis的序列化器和RedisTemplate
我们需要一种<String, Integer>类型的RedisTemplate,并且要求他的value能够灵活地在java中作为integer 而在redis中作为string,而不用通过我们手工转换,同时也能保证在redis服务器上的可读性. springboot上的redis整合api中提供了愚蠢的RedisTemplate<String, Integer> 由于Integer的序列化等问题显然不符合我们的要求,只能定制 一个自己的RedisTemplate 和序列化器,我习惯建一个config包,在该包的类中通过bean方法创建自己的配置bean
2.1.序列化器
定义一个类 实现RedisSerializer<Integer>接口,重写其序列化和反序列化方法
实现思路很简单,因为我们的目的是在redis中以string字符储存, 在java中以integer的形式出现,在序列化时候将integer转换为string再进行序列化,反序列化时将序列化后的string转换成integer即可
  
   - 
    
     
    
    
     
      @Override
     
    
- 
    
     
    
    
     
      public 
      byte[] serialize(
      @Nullable Integer integer) 
      throws SerializationException {
     
    
- 
    
     
    
    
         
      return integer== 
      null ? 
      null : integer.toString().getBytes(StandardCharsets.UTF_8);
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      @Override
     
    
- 
    
     
    
    
     
      public Integer deserialize(@Nullable byte[] bytes) throws SerializationException {
     
    
- 
    
     
    
    
         
      return bytes == 
      null ? 
      null : Integer.valueOf(
      new String(bytes, 
     
    
- 
    
     
    
    
     
              StandardCharsets.UTF_8));
     
    
- 
    
     
    
    
     
      }
     
    
2.2.RedisTemplate
我们可以简单地创建一个RedisTemplate<String, Integer> 对象,对其key采用string序列化策略(注意,不要使用愚蠢的jdk默认序列化器,其序列化策略并不会原原本本地将string对象作为字符地byte数组存储,而且将其序列化为一种远古人类使用地奇怪符号) 对其value采用我们上文自定义的序列化器,所以核心的操作是
setKeySerializer(new StringRedisSerializer());
setValueSerializer(intRedisSerializer);
全部代码如下
  
   - 
    
     
    
    
     
      @Configuration
     
    
- 
    
     
    
    
     
      public 
      class RedisConfig {
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      @Autowired
     
    
- 
    
     
    
    
     
          RedisSerializer<Integer> intRedisSerializer;
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      @Bean("intRedisTemplate")
     
    
- 
    
     
    
    
         
      public RedisTemplate<String, Integer> IntRedisTemplate(RedisConnectionFactory rcf){
     
    
- 
    
     
    
    
     
              RedisTemplate<String, Integer> re = 
      new RedisTemplate();
     
    
- 
    
     
    
    
     
              re.setConnectionFactory(rcf);
     
    
- 
    
     
    
    
     
              re.setKeySerializer(
      new StringRedisSerializer());
     
    
- 
    
     
    
    
     
              re.setValueSerializer(intRedisSerializer);
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
             
      return re;
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
     
      }
     
    
说明:我将前面的integer序列化器命名为intRedisSerializer,注入了该RedisTemplate中
3.侦探类实现检测http攻击的核心功能
聪明的你可能已经发现字体的颜色变深了,这并不是因为我不知道怎么改字体颜色,而是我们的核心代码要开始了!!!
侦探(HttpDetective)这个名字花了我近10分钟, 该接口只声明了一个inspection(String ip)方法, 决定这个ip是否能访问你的服务器, 然后我们再创建HttpDetectiveImpl来具体实现他的功能,我们使用了一种简单可容错的策略,忽略了异步可能导致的实际参数误差,但是这并不会影响我们的功能和安全性. 具体代码如下
  
   - 
    
     
    
    
     
      @Component("httpDetective")
     
    
- 
    
     
    
    
     
      public 
      class HttpDetectiveImpl implements HttpDetective {
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      @Autowired
     
    
- 
    
     
    
    
         
      private RedisTemplate<String, Integer> intRedisTemplate;
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      /**单位均为毫秒*/
     
    
- 
    
     
    
    
         
      private 
      final 
      int RECORD_TIME = 
      1000;
     
    
- 
    
     
    
    
         
      private 
      final 
      int ALLOW_TIMES = 
      6;
     
    
- 
    
     
    
    
         
      private 
      final 
      int REFUSE_TIME = 
      180000;
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      @Override
     
    
- 
    
     
    
    
         
      public boolean inspection(String ip) {
     
    
- 
    
     
    
    
     
              Integer times = intRedisTemplate.opsForValue().get(ip);
     
    
- 
    
     
    
    
             
      if(times == 
      null){
     
    
- 
    
     
    
    
     
                  System.out.println(
      "有正常人进入");
     
    
- 
    
     
    
    
     
                  intRedisTemplate.opsForValue().set(ip, 
      1, RECORD_TIME, TimeUnit.MILLISECONDS);
     
    
- 
    
     
    
    
                 
      return 
      true;
     
    
- 
    
     
    
    
     
              }
      else{
     
    
- 
    
     
    
    
                 
      if(times >= ALLOW_TIMES){
     
    
- 
    
     
    
    
     
                      System.out.println(
      "认定为入侵行为 拦截访问 并且禁止目标短时间内再次访问并且记录 入侵者ip"+ ip);
     
    
- 
    
     
    
    
     
                      intRedisTemplate.opsForValue().set(ip, ALLOW_TIMES, REFUSE_TIME, TimeUnit.MILLISECONDS);
     
    
- 
    
     
    
    
                     
      return 
      false;
     
    
- 
    
     
    
    
     
                  }
      else{
     
    
- 
    
     
    
    
     
                      System.out.println(
      "有可疑人进入. "+times);
     
    
- 
    
     
    
    
     
                      intRedisTemplate.opsForValue().increment(ip);
     
    
- 
    
     
    
    
                     
      return 
      true;
     
    
- 
    
     
    
    
     
                  }
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
     
      }
     
    
我们将前面的RedisTemplate注入侦探类作为侦探类的工具,为了方便后面的修改,我们定义了三个常量,单位都是毫秒
    RECORD_TIME:  检查http访问的时间间隔
     ALLOW_TIMES :  检测时间间隔内的访问的次数
     REFUSE_TIME:   对判定为入侵者的封禁时间
我们将客户端传入的ip作为redis的键,当客户端首次访问时候,会创建该键值对,并且设置其生存周期 也就是RECORD_TIME,我们这里设置为一秒,这一秒内 用户每当再次访问接口,对应的value就会自己加一,如果在生存周期内值加到ALLOW_TIMES时,会将其设为入侵者,并且其在REFUSE_TIME时间内无法再访问我们的url.即对该ip封禁的时间内都会返回false,并且若入侵者继续尝试访问http接口时都会刷新封禁时间,这里将其注册为名字为httpDetective的bean
4.拦截器中挂载该侦探类,实现http拦截检测
4.1 自定义拦截器
由于拦截器通过返回true和false来决定是否发行,这里只需要直接注入上面的侦探类返回其inspection方法即可.
  
   - 
    
     
    
    
     
      @Autowired
     
    
- 
    
     
    
    
     
      HttpDetective httpDetective;
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      @Bean
     
    
- 
    
     
    
    
     
      public HandlerInterceptor visitorRegistration(){
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      return 
      new HandlerInterceptor(){
     
    
- 
    
     
    
    
             
      @Override
     
    
- 
    
     
    
    
             
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     
    
- 
    
     
    
    
                 
      return httpDetective.inspection(request.getRemoteAddr());
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          };
     
    
- 
    
     
    
    
     
      }
     
    
4.3 在springboot中注册拦截器
springboot注册拦截器非常方便,只要继承了WebMvcConfigurer并且在其 addInterceptors(InterceptorRegistry registry) 方法中添加改拦截器即可,注意把该类注解为@Configuratio, 这里不必多说
  
   - 
    
     
    
    
     
      @Configuration
     
    
- 
    
     
    
    
     
      public 
      class WebMvcConfig implements WebMvcConfigurer {
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      @Resource
     
    
- 
    
     
    
    
     
          HandlerInterceptor visitorRegistration;
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      @Override
     
    
- 
    
     
    
    
         
      public void addInterceptors(InterceptorRegistry registry) {
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
              System.out.println(
      "添加拦截器");
     
    
- 
    
     
    
    
             
      // TODO Auto-generated method stub
     
    
- 
    
     
    
    
     
              registry.addInterceptor(visitorRegistration)
     
    
- 
    
     
    
    
                     
      // 拦截路劲
     
    
- 
    
     
    
    
     
                      .addPathPatterns(
      "/**");
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
     
      }
     
    
三、功能测试
我使用已经开发出雏形的二手商城为例子,先开启服务器

服务器正常启动
打开浏览器,疯狂刷http(很多人可能远远达不到我的手速,也可以选择通过编写程序刷url)


我们可以看到 在我一秒20刷的http请求中,在第六次之后的请求全部被拦截下来,并且3分钟内无法再访问
服务器中的redis也有对应的redis记录

四、总结和后语
虽然用屁股想都知道肯定有相关功能而且稳定高效的框架,但是我仍然喜欢自己亲自动手实现自己突然的想法或者以前的想法,代码中可能会因为springboot框架整体的异步结构会出现一定的偏差,但是这点并不会对其安全性有影响,而且有兴趣的人也可以对HttpDetective接口做更加稳定或者高效的实现,这是我认真写的第一张csdn 由于不怎么熟练,花了我快三个小时的时间完成.文章中出现的错别字请见谅,作者技术较菜,有什么说的不对的地方或者建议请大佬们多指教
转载:https://blog.csdn.net/pass_JMC/article/details/109392153
