【SpringBoot】限制IP访问频率

引言

显示中存在恶意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

你可能感兴趣的:(………J2SE)