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

This tutorial explains how to implement IP‑and‑URL rate limiting in a Spring Boot application by creating a custom HandlerInterceptor that tracks request counts in Redis, uses a distributed lock to enforce limits, and registers the interceptor to protect services from malicious or excessive traffic.

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

In a Spring Boot project, the article demonstrates how to protect services from malicious requests by limiting the number of accesses to a specific URL from the same IP within a given time window.

The solution uses a custom HandlerInterceptor that records request counts in Redis and disables the IP when the limit is exceeded, returning a JSON error response.

The core interceptor code is:

@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 { }

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

RedisUtil provides distributed‑lock utilities used by the interceptor:

@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, the interceptor is registered in a configuration class:

@Configuration
@Slf4j
public class MyWebAppConfig extends WebMvcConfigurerAdapter {
    @Bean
    IpUrlLimitInterceptor getIpUrlLimitInterceptor() {
        return new IpUrlLimitInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getIpUrlLimitInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}
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.

BackendredisSpring Bootdistributed-lockInterceptorrate limiting
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.