在实际项目中,往往需要对一些公开的接口进行限制在一定时间内的访问次数,避免他人的恶意攻击占用系统资源。
方案:设置拦截器,每次访问记录ip地址+访问URL作为key,使用redis按key查询是否有访问记录。如果未有访问redis记录,redis记录这次访问,如果redis查询到有访问次数,比较系统限制次数是否大于现在访问次数,大于则放行,小于等于则拒绝。(redis存储带过期时间,过期后自动清除)
String key = IpUtils.getIpAddr(request) + request.getRequestURI();
Integer maxLimit = redisTemplate.opsForValue().get(key);
if (maxLimit == null) {
redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS); //set时一定要加过期时间
} else if (maxLimit < limit) {
redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-lang3
3.9
com.alibaba
fastjson
1.2.54
org.projectlombok
lombok
true
2.application.properties配置Redis服务信息
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=5000
3.准备封装所需要工具类
IpUtils
/**
* 获取IP方法
*
*/
public class IpUtils
{
public static String getIpAddr(HttpServletRequest request)
{
if (request == null)
{
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getRemoteAddr();
}
//ip = EscapeUtil.clean(ip);// 清除Xss特殊字符
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
}
4.注解类用于标注需要限制的接口
默认5秒内限制最多5次访问
@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
int limit() default 5;
int sec() default 5;
}
5.编写拦截器类
AccessLimitInterceptor
无AccessLimit注解放行,有则拦截处理
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate redisTemplate; //使用RedisTemplate操作redis
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (!method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int limit = accessLimit.limit();
int sec = accessLimit.sec();
String key = IpUtils.getIpAddr(request) + request.getRequestURI();
Integer maxLimit = redisTemplate.opsForValue().get(key);
if (maxLimit == null) {
redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS); //set时一定要加过期时间
} else if (maxLimit < limit) {
redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);
} else {
output(response, "请求太频繁,请稍等再试!");
return false;
}
}
return true;
}
public void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=utf-8");
Map map=new HashMap<>();
map.put("code",2020);
map.put("msg",msg);
ObjectMapper om = new ObjectMapper();
PrintWriter out = response.getWriter();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
@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 {
}
}
6.注册拦截器,并设置需要拦截的URL
@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
@Autowired
AccessLimitInterceptor accessLimitInterceptor;
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(accessLimitInterceptor).addPathPatterns("/**");
}
}
7.测试接口类
@RestController
public class TestController {
@AccessLimit
@GetMapping("/get1")
public Object get(){
System.out.println("get1");
Map map=new HashMap<>();
map.put("code",2021);
map.put("msg","success get1");
return map;
}
@GetMapping("get2")
public Object get2(){
System.out.println("get2");
Map map=new HashMap<>();
map.put("code",2022);
map.put("msg","success get2");
return map;
}
}
结果:
get1
5秒内连续访问超5次,访问失败