springspring-cloud-gatewayrate-limiting

How to implement Spring Cloud Gateway Rate Limiting, For the same API/URL with different Rate Limiting values for different users?


I have some microservices with Spring Cloud, Now I have to implement ratel-limit for the API on the cloud gateway based on the user name.

With the help of KeyResolver, I extract the user from the requester JWT token and implemented RedisRateLimiter with the same rate-limit value (consumer50tps) for all users as below, It's working fine.

Now the Requirement is I have to set rate-limit values based on the user subscription, Whereas for the same api/url different users get different limit values (consumer50tps or consumer100tps).

current code what is working fine for a single limitation value (consumer 50tps) for all Users.

    @Bean
    @Primary
    public RedisRateLimiter consumer50tps() {
    return new RedisRateLimiter(50, 50, 1);
    }

    @Bean
    public RedisRateLimiter consumer100tps() {
    return new RedisRateLimiter(100, 100, 1);
    }

    @Bean
    public KeyResolver userKeyResolver1() {
    return exchange -> {
        // Implement logic to fetch JWT and get user name
    };
    }

    @Bean
    public RouteLocator appRouteConfig(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(p -> p.path("/microservice-one/api1/**")
                    .filters(f -> f
                            .rewritePath("/microservice-one/(?<segment>.*)", "/${segment}")
                            .requestRateLimiter(r -> r.setRateLimiter(consumer50tps())
                                    .setKeyResolver(userKeyResolver())
                            ))
                    .uri("lb://microservice-one"))
            .build();
    }
    
    
    
    
    

Solution

  • Simple way we can do it, create a customFilter with the help of GatewayFilter as:

    @Component
    public class MyRateLimiterFilter implements GatewayFilter {
    
        private final UserKeyResolver userKeyResolver;
        private final ReactiveRedisTemplate<String, String> redisTemplate;
    
        public MyRateLimiterFilter(ReactiveRedisTemplate<String, String> redisTemplate, UserKeyResolver userKeyResolver) {
        this.redisTemplate = redisTemplate;
        this.userKeyResolver = userKeyResolver;
    
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return userKeyResolver.resolve(exchange)
                .flatMap(key -> {
                    return redisTemplate.opsForValue().increment(key)
                            .flatMap(tokens -> {
                                if (tokens == 1) return redisTemplate.expire(key, Duration.ofMillis(1000)).thenReturn(tokens);
                                return Mono.just(tokens);
                            })
                            .flatMap(tokens -> {
                                int maxReqPerSec = 1;
    
     // Implement rate limit based on user here, in this case "key" returns the user ID from JWT.
     // You can inject the user from anywhere like user-service or read from any properties file and set the limit !
     
            if(key.equals("consumer50tps")){
              maxReqPerSec = 50;
            }else if(key.equals("consumer100tps")){
               maxReqPerSec = 100;
            }
            
            
                                if (tokens <= maxReqPerSec) {
                                    return chain.filter(exchange);
                                } else {
                                    LOGGER.info("Exceeded request limit for the user: "+key + ", current number of request: "+tokens);
                                    exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                                    return exchange.getResponse().setComplete();
                                }
                            });
                });
        }
    
    }
    

    and then add this filter on RouteLocator on specific service or APIs as and enjoy it !!

    @Bean
    public RouteLocator appRouteConfig(RouteLocatorBuilder builder, MyRateLimiterFilter myRateLimiter) {
    return builder.routes()
                     .route(p -> p.path("/microservice-one/**")
                        .filters(f -> f
                                .filter(myRateLimiter)
                                .rewritePath("/microservice-one/(?<segment>.*)", "/${segment}")
                         )
                        .uri("lb://microservice-one"))
            .build();
    }