Implementing Interface Rate Limiting with Spring Interceptor and Redis

This article demonstrates how to implement API request throttling in a Spring Boot application using a custom Interceptor and Redis, covering basic principles, configuration, custom annotations, reflection for per‑endpoint limits, handling path parameters, and addressing timing logic nuances.

Top Architect
Top Architect
Top Architect
Implementing Interface Rate Limiting with Spring Interceptor and Redis

Introduction: The article describes a demo of interface rate limiting using a Spring Interceptor and Redis.

Principle

Combine IP address and URI as a unique key for each visitor.

Intercept requests in the Interceptor, count accesses in Redis, and block when exceeding limits.

Project address: https://github.com/Tonciy/interface-brush-protection . Apifox credentials are provided in the article.

Implementation

The core Interceptor code is shown below:

/**  
 * @author: Zero  
 * @time: 2023/2/14  
 * @description: 接口防刷拦截处理  
 */
@Slf4j
public class AccessLimintInterceptor implements HandlerInterceptor{  
    @Resource
    private RedisTemplate<String, Object> redisTemplate;  
    /**  
     * 多长时间内  
     */
    @Value("${interfaceAccess.second}")  
    private Long second = 10L;  
    /**  
     * 访问次数  
     */
    @Value("${interfaceAccess.time}")  
    private Long time = 3L;  
    /**  
     * 禁用时长--单位/秒  
     */
    @Value("${interfaceAccess.lockTime}")  
    private Long lockTime = 60L;  
    /**  
     * 锁住时的key前缀  
     */
    public static final String LOCK_PREFIX = "LOCK";  
    /**  
     * 统计次数时的key前缀  
     */
    public static final String COUNT_PREFIX = "COUNT";  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        String uri = request.getRequestURI();  
        String ip = request.getRemoteAddr(); // 这里忽略代理软件方式访问,默认直接访问,也就是获取得到的就是访问者真实ip地址  
        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) {  
                    // 放行,访问次数 + 1  
                    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 configuration allows setting the time window, maximum accesses, and lock duration via properties.

Self‑question

Although the basic implementation works, it lacks flexibility for different interfaces and path parameters.

Solutions

1. Use mapping rules to apply the Interceptor only to selected endpoints.

2. Define a custom annotation @AccessLimit and use reflection to read per‑method or per‑class limits, enabling different x‑seconds, y‑times, and lock durations.

Custom annotation implementation code:

/**  
 * @author: Zero  
 * @time: 2023/2/14  
 * @description: 接口防刷拦截处理  
 */
@Slf4j
public class AccessLimintInterceptor implements HandlerInterceptor{  
    @Resource
    private RedisTemplate<String, Object> redisTemplate;  
    /**  
     * 锁住时的key前缀  
     */
    public static final String LOCK_PREFIX = "LOCK";  
    /**  
     * 统计次数时的key前缀  
     */
    public static final String COUNT_PREFIX = "COUNT";  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        // 自定义注解 + 反射 实现, 版本 2.0  
        if (handler instanceof HandlerMethod) {  
            // 访问的是接口方法,转化为待访问的目标方法对象  
            HandlerMethod targetMethod = (HandlerMethod) handler;  
            // 获取目标接口方法所在类的注解@AccessLimit  
            AccessLimit targetClassAnnotation = targetMethod.getMethod().getDeclaringClass().getAnnotation(AccessLimit.class);  
            boolean isBrushForAllInterface = false;  
            String ip = request.getRemoteAddr();  
            String uri = request.getRequestURI();  
            long second = 0L;  
            long maxTime = 0L;  
            long forbiddenTime = 0L;  
            if (!Objects.isNull(targetClassAnnotation)) {  
                log.info("目标接口方法所在类上有@AccessLimit注解");  
                isBrushForAllInterface = true;  
                second = targetClassAnnotation.second();  
                maxTime = targetClassAnnotation.maxTime();  
                forbiddenTime = targetClassAnnotation.forbiddenTime();  
            }  
            // 取出目标方法中的 AccessLimit 注解  
            AccessLimit accessLimit = targetMethod.getMethodAnnotation(AccessLimit.class);  
            if (!Objects.isNull(accessLimit)) {  
                // 需要进行防刷处理,接下来是处理逻辑  
                second = accessLimit.second();  
                maxTime = accessLimit.maxTime();  
                forbiddenTime = accessLimit.forbiddenTime();  
                if (isForbindden(second, maxTime, forbiddenTime, ip, uri)) {  
                    throw new CommonException(ResultCode.ACCESS_FREQUENT);  
                }  
            } else {  
                // 目标接口方法处无@AccessLimit注解,但还要看看其类上是否加了(类上有加,代表针对此类下所有接口方法都要进行防刷处理)  
                if (isBrushForAllInterface && isForbindden(second, maxTime, forbiddenTime, ip, uri)) {  
                    throw new CommonException(ResultCode.ACCESS_FREQUENT);  
                }  
            }  
        }  
        return true;  
    }  
    /**  
     * 判断某用户访问某接口是否已经被禁用/是否需要禁用  
     * @param second 多长时间 单位/秒  
     * @param maxTime 最大访问次数  
     * @param forbiddenTime 禁用时长 单位/秒  
     * @param ip 访问者ip地址  
     * @param uri 访问的uri  
     * @return true为需要禁用  
     */
    private boolean isForbindden(long second, long maxTime, long forbiddenTime, String ip, String uri) {  
        String lockKey = LOCK_PREFIX + ip + uri; //如果此ip访问此uri被禁用时的存在Redis中的key  
        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 < maxTime) {  
                    redisTemplate.opsForValue().increment(countKey);  
                } else {  
                    log.info("{}禁用访问{}", ip, uri);  
                    redisTemplate.opsForValue().set(lockKey, 1, forbiddenTime, TimeUnit.SECONDS);  
                    redisTemplate.delete(countKey);  
                    return true;  
                }  
            }  
        } else {  
            // 此用户访问此接口已被禁用  
            return true;  
        }  
        return false;  
    }  
}

Additional considerations include handling path parameters by using method names (or class + method) instead of raw URIs, and obtaining the real client IP when behind proxies.

Conclusion

The article provides a comprehensive guide to building a configurable API rate‑limiting mechanism in Spring Boot, discussing pitfalls and advanced techniques such as custom annotations, reflection, and mapping rules.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaredisInterceptorrate limiting
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.