乐趣区

关于java:简易实现防攻击拦截器

懈怠篇,不想写字,间接贴代码

采纳拦截器的形式拦挡申请并计算,拦挡规定是在 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());
    }

}
退出移动版