Implementing IP+URL Rate Limiting with Spring Boot Interceptor and Redis Distributed Lock
This guide 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.
When a service is exposed to the Internet, it must defend against malicious requests and brute‑force attacks; this article shows how to use a Spring Boot interceptor together with Redis to limit the number of requests from the same IP to a specific URL within a given time window.
First, a custom interceptor IpUrlLimitInterceptor is created. It defines constants for lock keys, request counters, limits (5 requests per second) and lock duration (60 seconds). In preHandle it logs the request, checks whether the IP is already locked, and either returns an error response or records the request count. Helper methods ipIsLock , addRequestTime and returnJson interact with a RedisUtil bean to store counters and acquire distributed locks.
/**
* @package: com.technicalinterest.group.interceptor
* @className: IpUrlLimitInterceptor
* @description: ip+url重复请求现在拦截器
*/
@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, (long) 1);
if (time >= LIMIT_TIMES) {
redisUtil.getLock(LOCK_IP_URL_KEY + ip, ip, IP_LOCK_TIME);
return false;
}
} else {
redisUtil.getLock(key, (long) 1, 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 RedisUtil class provides getLock and releaseLock methods that execute Lua scripts via RedisTemplate to implement atomic lock acquisition and release.
/**
* @package: com.shuyu.blog.util
* @className: RedisUtil
*/
@Component
@Slf4j
public class RedisUtil {
private static final Long SUCCESS = 1L;
@Autowired
private RedisTemplate
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
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
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 Spring configuration class by defining a bean for IpUrlLimitInterceptor and adding it to the InterceptorRegistry with a path pattern of "/**" . After this configuration the rate‑limiting logic becomes active for all endpoints.
@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);
}
}The article notes that developers can write a simple loop to test the functionality and provides a reference to the original CSDN blog post.
Source: blog.csdn.net/wang_shuyu/article/details/102531940
Top Architecture Tech Stack
Sharing Java and Python tech insights, with occasional practical development tool tips.
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.