显示中存在恶意ip频繁请求情况,本文通过自定义注解+拦截器实现限制ip访问的频率
1. 添加pom依赖
org.springframework
spring-aspects
4.3.10.RELEASE
2. 添加自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface RequestLimit {
/**
* 允许访问的最大次数
*/
int count() default Integer.MAX_VALUE;
/**
* 时间段,单位为毫秒,默认值一分钟
*/
long time() default 60000;
}
3. 添加自定义异常
public class RequestLimitException extends NestedRuntimeException {
public RequestLimitException(){
super("HTTP请求超出设定的限制");
}
public RequestLimitException(String msg) {
super(msg);
}
}
4. 添加自定义拦截器
interceptor
@Slf4j
@Aspect
@Component
public class RequestLimitInterceptor {
@Before("within(@org.springframework.web.bind.annotation.RequestMapping * || @javax.ws.rs.Path *) && @annotation(limit)")
public void requestLimit(final JoinPoint joinPoint, RequestLimit limit) throws RequestLimitException {
try {
// 获取 HttpServletRequest
Object[] args = joinPoint.getArgs();
HttpServletRequest request = null;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof HttpServletRequest) {
request = (HttpServletRequest) args[i];
break;
}
}
if (request == null) {
throw new RequestLimitException("调用方法中缺少HttpServletRequest参数");
}
String ip = HttpUtil.getIpByRequest(request);
String url = request.getRequestURL().toString();
String key = "req_limit_".concat(url).concat(ip);
IMap<String, Object> cache = CacheData.getInstance().getRequestLimitCache();
String value = (String) cache.get(key);
if (null == value) {
value = "1_" + System.currentTimeMillis();
cache.put(key, value, limit.time(), TimeUnit.MILLISECONDS);
} else {
String[] s = value.split("_");
int count = Integer.parseInt(s[0]);
if (count > limit.count()) {
log.info("用户IP[{}], 访问地址[{}], 超过了限定的次数[{}]", ip, url, limit.count());
throw new RequestLimitException();
}
value = (count + 1) + "_" + s[1];
long last = limit.time() - (System.currentTimeMillis() - Long.parseLong(s[1]));
if (last > 0) {
cache.put(key, value, last, TimeUnit.MILLISECONDS);
}
}
} catch (RequestLimitException e) {
throw e;
} catch (Exception e) {
log.error("发生异常", e);
}
}
}
http utils
@Slf4j
public class HttpUtil {
public static String getIpByRequest(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.contains(",")) {
ip = ip.split(",")[0];
}
}
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("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
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();
}
return ip;
}
}
5. 使用
Jersey方式
@RequestLimit(time = 3000,count = 2)
@GET
@Path("/test")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response test(@Context HttpServletRequest request) {
return Response.status(200).entity(new Date()).build();
}
Spring MVC 方式
@RequestLimit(time = 3000,count = 2)
@GetMapping(value = "/test")
public RespEntity test(HttpServletRequest request) {
return RespEntity.success(new Date());
}
本文优化了拦截方式,可以同时拦截springMVC和Jersey。优化了ip次数检查,利用的hazelcast过期key的方式,当然redis也可以实现。
参考:
https://blog.csdn.net/It_BeeCoder/article/details/94303699
https://bbs.csdn.net/topics/392154383
https://blog.csdn.net/qq_37272886/article/details/88553962