Implementing IP+URL Rate Limiting with Spring Boot Interceptor and Redis Distributed Lock

This article demonstrates how to protect a Spring Boot service from malicious requests by creating a custom interceptor that tracks URL‑IP request counts, uses Redis for distributed locking, and disables offending IPs after a configurable threshold, with full code examples and configuration steps.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Implementing IP+URL Rate Limiting with Spring Boot Interceptor and Redis Distributed Lock

When a service is exposed to the Internet, it must defend against malicious requests and brute‑force attacks. This tutorial shows how to use a Spring Boot interceptor together with Redis to limit the number of accesses to a specific url+ip combination within a given time window and temporarily block abusive IPs.

Core Interceptor

/**
 * @package: com.technicalinterest.group.interceptor
 * @className: IpUrlLimitInterceptor
 * @description: ip+url duplicate request interceptor
 */
@Slf4j
public class IpUrlLimitInterceptor implements HandlerInterceptor {
    private RedisUtil getRedisUtil() {
        return SpringContextUtil.getBean(RedisUtil.class);
    }
    private static final String LOCK_IP_URL_KEY = "lock_ip_";
    private static final String IP_URL_REQ_TIME = "ip_url_times_";
    private static final long LIMIT_TIMES = 5;
    private static final int IP_LOCK_TIME = 60;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("request uri={}, ip={}", request.getRequestURI(), IpAdrressUtil.getIpAdrress(request));
        if (ipIsLock(IpAdrressUtil.getIpAdrress(request))) {
            log.info("ip blocked={}", IpAdrressUtil.getIpAdrress(request));
            ApiResult result = new ApiResult(ResultEnum.LOCK_IP);
            returnJson(response, JSON.toJSONString(result));
            return false;
        }
        if (!addRequestTime(IpAdrressUtil.getIpAdrress(request), request.getRequestURI())) {
            ApiResult result = new ApiResult(ResultEnum.LOCK_IP);
            returnJson(response, JSON.toJSONString(result));
            return false;
        }
        return true;
    }
    // postHandle and afterCompletion omitted for brevity
    private Boolean ipIsLock(String ip) {
        RedisUtil redisUtil = getRedisUtil();
        return redisUtil.hasKey(LOCK_IP_URL_KEY + ip);
    }
    private Boolean addRequestTime(String ip, String uri) {
        String key = IP_URL_REQ_TIME + ip + uri;
        RedisUtil redisUtil = getRedisUtil();
        if (redisUtil.hasKey(key)) {
            long time = redisUtil.incr(key, 1L);
            if (time >= LIMIT_TIMES) {
                redisUtil.getLock(LOCK_IP_URL_KEY + ip, ip, IP_LOCK_TIME);
                return false;
            }
        } else {
            redisUtil.getLock(key, 1L, 1);
        }
        return true;
    }
    private void returnJson(HttpServletResponse response, String json) throws Exception {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/json; charset=utf-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.print(json);
        } catch (IOException e) {
            log.error("Interceptor response error", e);
        }
    }
}

The interceptor checks whether the incoming IP is already locked; if not, it records the request count for the url+ip pair. When the count exceeds LIMIT_TIMES (default 5 requests per second), the IP is locked for IP_LOCK_TIME seconds (default 60 seconds) using a Redis distributed lock.

Redis Utility (Distributed Lock)

@Component
public class RedisUtil {
    private static final Long SUCCESS = 1L;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /** Get lock */
    public boolean getLock(String lockKey, Object value, int expireTime) {
        try {
            String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
            RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime);
            return SUCCESS.equals(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    /** Release lock */
    public boolean releaseLock(String lockKey, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
        Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
        return SUCCESS.equals(result);
    }
    // Additional helper methods such as incr, hasKey, getLock (for counting) are omitted for brevity
}

Redis is used both for counting requests (by incrementing a key) and for implementing the distributed lock that guarantees thread‑safety across multiple service instances.

Registering the Interceptor

@Configuration
@Slf4j
public class MyWebAppConfig extends WebMvcConfigurerAdapter {
    @Bean
    public IpUrlLimitInterceptor getIpUrlLimitInterceptor() {
        return new IpUrlLimitInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getIpUrlLimitInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

After adding the interceptor to the Spring MVC configuration, every incoming request passes through the rate‑limiting logic.

Developers can adjust the constants LIMIT_TIMES, IP_LOCK_TIME, and the Redis key prefixes to fit their own security requirements.

Overall, this solution provides a lightweight, Redis‑backed mechanism to mitigate abusive traffic on Spring Boot applications.

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.

redisSpring Bootdistributed-lockInterceptor
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow 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.