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