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());
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。