Spring Cloud Gateway通过全局过滤器实现接口防刷

2023年 7月 24日 118.2k 0

环境:Spring Boot2.7.12 + Spring Cloud2021.0.7

1 概念

通过某种机制实现对系统中的某些接口在规定的时间段内只能让某个具体的客户端访问指定次数,超出次数,就不让访问了。等待指定的时间到期后又能继续访问接口;这里需要注意的是是控制到每一个具体的接口上,所以必须确定两个要素:

  • 客户端是谁
  • 访问的接口
  • 2 实现原理

    可以通过2种方式实现:

  • 通过网关可以控制到所有的服务
  • 通过AOP该方案只能针对具体的每一个服务,代码重复,如果通过
  • 本篇文章我们通过网关实现,那接下来就是考虑上该如何去记录当前客户端访问的具体接口在指定的时间内已经访问了多少次了?通过两种方式:

  • JVM层该种实现方式,你需要自己实现时效性的检查,实现麻烦
  • 通过RedisRedis本身就可以对Key设置时效性,所以非常的方便。本文通过Redis实现。
  • 通过 Redis 记录访问请求的次数,每次访问都进行递减,如果次数小于0就返回错误信息,当到了指定的时效则Redis会对过期的key进行自动删除。

    3 代码实现

    Redis配置

    spring:
      redis:
        host: localhost
        port: 6379
        password: 123123
        database: 8
        lettuce:
          pool:
            maxActive: 8
            maxIdle: 100
            minIdle: 10
            maxWait: -1

    定义全局过滤器

    @Component
    public class BrushProofFilter implements GlobalFilter, Ordered {
    
    
      private final ReactiveStringRedisTemplate reactiveStringRedisTemplate ;
      
      public BrushProofFilter(ReactiveStringRedisTemplate reactiveStringRedisTemplate) {
        this.reactiveStringRedisTemplate = reactiveStringRedisTemplate ;
      }
      
      @Override
      public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取客户端的请求ip
        InetAddress address = exchange.getRequest().getRemoteAddress().getAddress();
        // 获取请求的URI
        String path = exchange.getRequest().getPath().toString() ;
        // 将其组合为Redis中的Key
        String key = ("ratelimiter:" + address + ":" + path) ;
        // 通过抛出异常的方式终止序列,然后通过自定义的WebExceptionHandler处理异常信息 
        return this.reactiveStringRedisTemplate.opsForValue()
            // 这里固定设置每30s,访问10次
            .setIfAbsent(key, "10", Duration.ofSeconds(30))
            .flatMap(exist -> {
              return this.reactiveStringRedisTemplate.opsForValue().decrement(key) ;
            })
            .doOnNext(num -> {
              if (num < 0) {
                throw new BrushProofException("你访问太快了") ;
              }
            })
            .then(chain.filter(exchange)) ;
      }
    
    
      @Override
      public int getOrder() {
        return -2 ;
      }
    
    
    }

    自定义异常

    public class BrushProofException extends RuntimeException {
    
    
      private static final long serialVersionUID = 1L;
      
      public BrushProofException(String message) {
        super(message) ;
      }
      
    }

    自定义异常处理句柄

    @Component
    public class RatelimiterWebExceptionHandler implements WebExceptionHandler {
    
    
      @Override
      public Mono handle(ServerWebExchange exchange, Throwable ex) {
        if (ex instanceof RatelimiterException re) {
          ServerHttpResponse response = exchange.getResponse() ;
          response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR) ;
          response.getHeaders().add("Content-Type", "text/html;") ;
          // 直接输出了,异常不进行传递
          return response.writeWith(Mono.just(response.bufferFactory().wrap(("你访问太快了").getBytes()))) ;
        }
        // 如果是其它异常,则将异常继续向下传递
        return Mono.error(ex) ;
      }
    
    
    }

    访问测试

    图片图片

    因为我这里没有这个接口,所以返回的是降级接口,也算是正常

    当超过10次后:

    Redis

    图片图片

    以客户端请求ip + path作为key

    图片图片

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论