懈怠篇,不想写字,间接贴代码
采纳拦截器的形式拦挡申请并计算,拦挡规定是在X时间段
内收到超过Y次申请
,就断定为攻打,申请又分为失常申请
和异样申请
。
这里为了简化组件引入老本,采纳内存存储
的形式,如果有须要能够改为其余存储形式(如redis)。申请次数采纳本地缓存(guava)+自定义的滑动窗口(看后面的文章)
来存储和计算。
@Slf4jpublic 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(); } }}
减少手动增加/删除IP黑名单的接口。为了平安起见,增加是否容许手动操作的开关,惯例工夫倡议敞开开关
@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]黑名单中!"; }}
配置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()); }}