Lazy articles, don’t want to write, just paste the code

Using the interceptor intercepts the request and calculates manner, blocking rules in received over the period of time X the Y time request, it is determined as an attack, a request is divided into normal request and exception request.
In order to simplify the cost of component introduction, the memory storage method is adopted, and other storage methods (such as redis) can be changed if necessary. The number of requests is stored and calculated local cache (guava) + custom sliding window (see previous article).

@Slf4j
public class AttackInterceptor extends HandlerInterceptorAdapter {

    private Limiter limiter;
    private ConcurrentHashSet<String> blockIps;

    @Value("#{new Boolean('${web.attackInterceptor.allowTimes.enable:false}')}")
    private boolean allowTimesAttackInterceptor;
    @Value("#{new Boolean('${web.attackInterceptor.errorAllowTimes.enable:true}')}")
    private boolean errorAllowTimesAttackInterceptor;
    @Value("${web.attackInterceptor.windowIntervalInMs:1000}")
    private int windowIntervalInMs;
    @Value("${web.attackInterceptor.allowTimes:20}")
    private int allowTimes;
    @Value("${web.attackInterceptor.errorAllowTimes:10}")
    private int errorAllowTimes;

    @Value("#{'${web.attackInterceptor.excludeFilterPath:}'.isEmpty() ? null : '${web.attackInterceptor.excludeFilterPath:}'.split(',')}")
    private List<String> excludeFilterPath;

    @PostConstruct
    public void init() {
        limiter = new Limiter(windowIntervalInMs);
        blockIps = new ConcurrentHashSet<>();
        if (excludeFilterPath == null) {
            excludeFilterPath = new ArrayList<>();
        }
        excludeFilterPath.add("/removeAttackBlackIp");
        excludeFilterPath.add("/addAttackBlackIp");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) {
        String ip = HttpIpUtils.getRealIP(request);
        try {
            if (doLimited(request, response)) {
                return false;
            }
            if (allowTimesAttackInterceptor && !limiter.get(ip).tryAcquire(AttackType.REQ, allowTimes)) {
                log.error("[AttackInterceptor]{}每秒请求超过限制次数,将被限制请求!", ip);
                blockIps.add(ip);
            }
            if (doLimited(request, response)) {
                return false;
            }
        } catch (Exception e) {
            log.error("AttackInterceptor preHandle [url:{}, ip:{}] error!", request.getRequestURL().toString(), ip, e);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception ex) {
        String ip = HttpIpUtils.getRealIP(request);
        try {
            int responseStatus = response.getStatus();
            if (!HttpStatus.valueOf(responseStatus).isError()) {
                return;
            }
            if (errorAllowTimesAttackInterceptor && !limiter.get(ip)
                    .tryAcquire(AttackType.ERROR_REQ, errorAllowTimes - 1)) {
                log.error("[AttackInterceptor]{}每秒返回异常的请求超过限制次数,将被限制请求!", ip);
                blockIps.add(ip);
            }
        } catch (Exception e) {
            log.error("AttackInterceptor afterCompletion [url:{}, ip:{}] error!", request.getRequestURL().toString(),
                    ip, e);
        }
    }

    /**
     * @param request
     * @param response
     * @return boolean
     * @author 
     * @date 
     */
    private boolean doLimited(HttpServletRequest request, HttpServletResponse response) {
        if (!allowTimesAttackInterceptor && !errorAllowTimesAttackInterceptor) {
            return false;
        }
        String uri = request.getRequestURI();
        if (excludeFilterPath.contains(uri)) {
            return false;
        }
        String ip = HttpIpUtils.getRealIP(request);
        if (blockIps.contains(ip)) {
            writeResult(response);
            log.warn("[url:{}, ip:{}] is blocked by AttackInterceptor!", request.getRequestURL().toString(), ip);
            return true;
        }
        return false;
    }

    /**
     * @param response
     * @author 
     * @date 
     */
    protected void writeResult(HttpServletResponse response) {
        PrintWriter writer = null;
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        try {
            writer = response.getWriter();
            writer.print(JSON.toJSONString(ResponseResult.fail("blocked!")));
            writer.flush();
        } catch (Exception e) {
            log.error("AttackInterceptor write blocked error!", e);
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

    /**
     * @param ip
     * @return boolean
     * @author 
     * @date 
     */
    public boolean removeBlockIp(String ip) {
        return blockIps.remove(ip);
    }

    /**
     * @param ip
     * @return boolean
     * @author 
     * @date 
     */
    public boolean addBlockIp(String ip) {
        if (blockIps.contains(ip)) {
            return false;
        }
        return blockIps.add(ip);
    }

    enum AttackType {
        REQ,
        ERROR_REQ
    }

    /**
     * @author 
     * @return null
     * @date
     */
    class Limiter extends AbstractLocalCache<String, SlidingWindow> {

        public static final int DEFAULT_CACHE_MAX_SIZE = 1000;
        public static final int DEFAULT_CACHE_EXPIRE = 360;
        private int windowIntervalInMs;

        public Limiter(int windowIntervalInMs) {
            super(CacheBuilder.newBuilder().maximumSize(DEFAULT_CACHE_MAX_SIZE)
                    .expireAfterAccess(DEFAULT_CACHE_EXPIRE, TimeUnit.SECONDS));
            this.windowIntervalInMs = windowIntervalInMs;
        }

        @Override
        protected SlidingWindow loadData(String key) {
            return new SlidingWindow.SlidingWindowBuilder()
                    .ofEventTypeClass(AttackType.class)
                    .ofBucketCount(10)
                    .ofWindowIntervalInMs(windowIntervalInMs)
                    .build();
        }
    }
}

Add an interface for manually adding/deleting IP blacklist. For safety, add whether to allow manual operation of the switch, it is recommended to turn off the switch at regular time

@RestController
@RequestMapping("")
public class WebController {

    @Value("${web.attackInterceptor.canOpt:false}")
    private boolean classOptAttackBlackIp;
    @Autowired
    private AttackInterceptor attackInterceptor;

    /**
     * @param ip
     * @return java.lang.String
     * @author 
     * @date 
     */
    @RequestMapping(value = "/removeAttackBlackIp",
            method = {RequestMethod.GET, RequestMethod.POST})
    public String removeAttackBlackIp(String ip) {
        if (!classOptAttackBlackIp) {
            return "禁止手动操作";
        }
        if (attackInterceptor.removeBlockIp(ip)) {
            return ip + "已经从[AttackInterceptor]黑名单中移除!";
        }
        return ip + "不在[AttackInterceptor]黑名单中!";
    }

    /**
     * @param ip
     * @return java.lang.String
     * @author 
     * @date 
     */
    @RequestMapping(value = "/addAttackBlackIp",
            method = {RequestMethod.GET, RequestMethod.POST})
    public String addAttackBlackIp(String ip) {
        if (!classOptAttackBlackIp) {
            return "禁止手动操作";
        }
        if (attackInterceptor.addBlockIp(ip)) {
            return ip + "已添加到[AttackInterceptor]黑名单中!";
        }
        return ip + "已经存在于[AttackInterceptor]黑名单中!";
    }
}

Configure Bean

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
public class WebConfiguration implements WebMvcConfigurer {

    @Bean
    public AttackInterceptor getAttackInterceptor() {
        return new AttackInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registration = registry.addInterceptor(new LogInterceptor());
        registration.excludePathPatterns("/error", "/druid/*", "/webjars/**", "/*.js", "/*.html",
                "/*.img", "/swagger-resources/**", "/webjars/**", "/v2/**",
                "/swagger-ui.html/**", "/csrf");
        registry.addInterceptor(getAttackInterceptor());
    }

}

noname
314 声望49 粉丝

一只菜狗