Implement Rate Limiting with Spring Cloud Gateway and Redis
This tutorial explains how to restrict client access to specific APIs within a time window using Spring Cloud Gateway and Redis, covering the concept, implementation options, and complete code examples for a Redis‑backed rate limiter.
1 Concept
Through a mechanism, limit the number of times a specific client can access a particular interface within a defined time window; exceeding the limit blocks access until the period expires. Two essential elements: the client identity and the target interface.
Client identity
Target interface
2 Implementation Principle
Two approaches: via gateway (covers all services) or via AOP (applies per service, leads to code duplication).
This article implements the gateway approach. To record how many times a client has accessed an interface within the time window, two methods are possible: JVM‑level (requires custom expiration logic) or Redis (leverages key expiration).
Using Redis, each request decrements a counter; when the count falls below zero, an error is returned. Redis automatically deletes expired keys.
3 Code Implementation
Redis configuration:
<code>spring:
redis:
host: localhost
port: 6379
password: 123123
database: 8
lettuce:
pool:
maxActive: 8
maxIdle: 100
minIdle: 10
maxWait: -1
</code>Global filter definition:
<code>@Component
public class BrushProofFilter implements GlobalFilter, Ordered {
private final ReactiveStringRedisTemplate reactiveStringRedisTemplate;
public BrushProofFilter(ReactiveStringRedisTemplate reactiveStringRedisTemplate) {
this.reactiveStringRedisTemplate = reactiveStringRedisTemplate;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Get client IP
InetAddress address = exchange.getRequest().getRemoteAddress().getAddress();
// Get request URI
String path = exchange.getRequest().getPath().toString();
// Combine as Redis key
String key = ("ratelimiter:" + address + ":" + path);
// Set limit: 10 requests per 30 seconds
return this.reactiveStringRedisTemplate.opsForValue()
.setIfAbsent(key, "10", Duration.ofSeconds(30))
.flatMap(exist -> {
return this.reactiveStringRedisTemplate.opsForValue().decrement(key);
})
.doOnNext(num -> {
if (num < 0) {
throw new BrushProofException("You are accessing too fast");
}
})
.then(chain.filter(exchange));
}
@Override
public int getOrder() {
return -2;
}
}
</code>Custom exception:
<code>public class BrushProofException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BrushProofException(String message) {
super(message);
}
}
</code>Custom exception handler:
<code>@Component
public class RatelimiterWebExceptionHandler implements WebExceptionHandler {
@Override
public Mono<Void> 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;charset=utf8");
return response.writeWith(Mono.just(response.bufferFactory().wrap(("You are accessing too fast").getBytes())));
}
return Mono.error(ex);
}
}
</code>Test results (images illustrate the request key and rate limiting behavior).
When the request count exceeds 10, access is blocked.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.