Implementing Interface Rate Limiting with Spring Interceptor and Redis in Java
This article demonstrates how to prevent API abuse in a Java Spring application by using a custom HandlerInterceptor combined with Redis to track request counts per IP and URI, covering basic implementation, configuration, custom annotations, reflection for flexible limits, and discusses potential pitfalls and improvements.
1. Introduction
This article describes a demo that implements API rate‑limiting using a Spring HandlerInterceptor and Redis. The goal is to prevent excessive requests to an interface by counting accesses per IP address and URI.
2. Principle
Combine the client IP and request URI as a unique key to identify a visitor’s request.
Intercept each request in the preHandle method, read the counter from Redis, and decide whether to allow the request or block it.
The following diagram (omitted) illustrates the workflow.
3. Project Resources
Project repository: https://github.com/Tonciy/interface-brush-protection
Apifox documentation (password: Lyh3j2Rv) is also provided.
4. Core Interceptor Code
@Slf4j
public class AccessLimintInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public static final String LOCK_PREFIX = "LOCK";
public static final String COUNT_PREFIX = "COUNT";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
String ip = request.getRemoteAddr(); // real IP when no proxy
String lockKey = LOCK_PREFIX + ip + uri;
Object isLock = redisTemplate.opsForValue().get(lockKey);
if (Objects.isNull(isLock)) {
String countKey = COUNT_PREFIX + ip + uri;
Object count = redisTemplate.opsForValue().get(countKey);
if (Objects.isNull(count)) {
log.info("首次访问");
redisTemplate.opsForValue().set(countKey, 1, second, TimeUnit.SECONDS);
} else {
if ((Integer) count < time) {
redisTemplate.opsForValue().increment(countKey);
} else {
log.info("{}禁用访问{}", ip, uri);
redisTemplate.opsForValue().set(lockKey, 1, lockTime, TimeUnit.SECONDS);
redisTemplate.delete(countKey);
throw new CommonException(ResultCode.ACCESS_FREQUENT);
}
}
} else {
throw new CommonException(ResultCode.ACCESS_FREQUENT);
}
return true;
}
}The time window, maximum request count, and lock duration are configurable via the application’s properties file.
5. Self‑Questioning
The basic implementation works, but it applies the same x seconds, y requests, and z lock time to all protected endpoints, which is not flexible enough for real projects.
5.1 Interceptor Mapping Rules
Spring allows interceptors to be mapped to specific URL patterns. By configuring the interceptor to match only the endpoints that need protection, we can limit its scope.
5.2 Custom Annotation + Reflection
Define a custom annotation @AccessLimit with attributes second, maxTime, and forbiddenTime. Apply the annotation either on controller methods or on the whole controller class. In the interceptor, use reflection to read the annotation values and enforce per‑endpoint limits.
@Slf4j
public class AccessLimintInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public static final String LOCK_PREFIX = "LOCK";
public static final String COUNT_PREFIX = "COUNT";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod targetMethod = (HandlerMethod) handler;
AccessLimit classAnno = targetMethod.getMethod().getDeclaringClass().getAnnotation(AccessLimit.class);
boolean classLevel = classAnno != null;
AccessLimit methodAnno = targetMethod.getMethodAnnotation(AccessLimit.class);
long second, maxTime, forbiddenTime;
if (methodAnno != null) {
second = methodAnno.second();
maxTime = methodAnno.maxTime();
forbiddenTime = methodAnno.forbiddenTime();
} else if (classLevel) {
second = classAnno.second();
maxTime = classAnno.maxTime();
forbiddenTime = classAnno.forbiddenTime();
} else {
return true; // no rate limit
}
String ip = request.getRemoteAddr();
String uri = request.getRequestURI();
if (isForbidden(second, maxTime, forbiddenTime, ip, uri)) {
throw new CommonException(ResultCode.ACCESS_FREQUENT);
}
}
return true;
}
private boolean isForbidden(long second, long maxTime, long forbiddenTime, String ip, String uri) {
String lockKey = LOCK_PREFIX + ip + uri;
Object isLock = redisTemplate.opsForValue().get(lockKey);
if (Objects.isNull(isLock)) {
String countKey = COUNT_PREFIX + ip + uri;
Object count = redisTemplate.opsForValue().get(countKey);
if (Objects.isNull(count)) {
redisTemplate.opsForValue().set(countKey, 1, second, TimeUnit.SECONDS);
} else {
if ((Integer) count < maxTime) {
redisTemplate.opsForValue().increment(countKey);
} else {
redisTemplate.opsForValue().set(lockKey, 1, forbiddenTime, TimeUnit.SECONDS);
redisTemplate.delete(countKey);
return true;
}
}
} else {
return true;
}
return false;
}
}This approach allows each endpoint to have its own rate‑limit parameters, and class‑level annotations provide a convenient way to apply the same limits to all methods in a controller.
6. Remaining Issues
Time‑window accuracy: The simple Redis key expiration method treats the first request as the start of the window, which may not reflect a true sliding‑window algorithm.
Path parameters: When the same endpoint is accessed with different path variables, the URI differs, causing separate counters. Solutions include ignoring path variables or using the method name (and class name) as the key.
Real IP acquisition: request.getRemoteAddr() may return the proxy address; extracting the client IP from headers (e.g., X‑Forwarded‑For) is necessary in production.
7. Summary
The article walks through a complete implementation of API rate limiting in a Spring MVC project, starting from a basic interceptor‑Redis solution, evolving to a flexible annotation‑based design, and finally discussing practical concerns such as sliding windows, path parameters, and real‑IP handling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
