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

This tutorial demonstrates how to protect Spring Boot services from malicious requests by creating a custom interceptor that tracks request counts per IP and URL, uses Redis for distributed locking, and disables abusive IPs after a configurable threshold within a defined time window.

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

When deploying services to the internet, it is essential to consider security against malicious requests and brute‑force attacks. This guide shows how to use a Spring Boot interceptor together with Redis to limit the number of requests from a single IP to a specific URL within a certain time frame.

The core idea is to record each request's IP and URL combination in Redis, increment a counter, and lock the IP if the request count exceeds a predefined limit (e.g., 5 requests per second). The interceptor checks the lock status before allowing the request to proceed.

Below is the custom interceptor implementation ( IpUrlLimitInterceptor) that performs the rate‑limiting logic:

/**
 * @package: com.technicalinterest.group.interceptor
 * @className: IpUrlLimitInterceptor
 * @description: ip+url重复请求现在拦截器
 * @author: Shuyu.Wang
 * @since: 0.1
 */
@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访问被禁止={}", 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;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}

    /**
     * 判断ip是否被禁用
     */
    private Boolean ipIsLock(String ip) {
        RedisUtil redisUtil = getRedisUtil();
        if (redisUtil.hasKey(LOCK_IP_URL_KEY + ip)) {
            return true;
        }
        return false;
    }

    /**
     * 记录请求次数
     */
    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 {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/json; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(json);
        } catch (IOException e) {
            log.error("LoginInterceptor response error ---> {}", e.getMessage(), e);
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
}

The interceptor relies on a RedisUtil class that provides distributed‑lock operations. The essential methods are getLock (to acquire a lock) and releaseLock (to release it). Below is a simplified version of the utility:

/**
 * @package: com.shuyu.blog.util
 * @className: RedisUtil
 */
@Component
@Slf4j
public class RedisUtil {
    private static final Long SUCCESS = 1L;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取锁
     */
    public boolean getLock(String lockKey, Object value, int expireTime) {
        try {
            log.info("添加分布式锁key={},expireTime={}", lockKey, expireTime);
            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);
            if (SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 释放锁
     */
    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);
        if (SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

Finally, register the interceptor in a Spring configuration class so that it applies to all request paths:

@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);
    }
}

With this setup, the application will automatically block IPs that exceed the configured request threshold, helping to mitigate brute‑force attacks and improve overall service security. Adjust the constants ( LIMIT_TIMES, IP_LOCK_TIME, etc.) to suit your specific requirements.

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.

Interceptordistributed-lockrate-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.