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