中间件 —— 黑白名单(Block Allow List)

中间件 —— 黑白名单(Block Allow List)


简介

  • 将黑白名单封装为一个中间件
  • 使用时可通过Apollo配置黑名单,白名单
  • 集成飞书机器人通知
  • 集成功能开关

实现

Apollo 配置

#黑名单(多个IP以逗号间隔,与白名单二选一配置,默认为空)
block.list = 
#白名单(多个IP以逗号间隔,与白名单二选一配置,默认为空)
allow.list = 
#是否开启Controller黑白名单验证(默认不开启)
block.allow.list.controller.check = false
#是否开启Dubbo Provider黑白名单验证(默认不开启)
block.allow.list.dubbo.provider.check = false
#是否开启飞书通知(默认不开启)
block.allow.list.fei.shu.notice = false
#飞书机器人ID(参照官方文档获取:https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN)
block.allow.list.fei.shu.robot.id = 

核心工具类

Controller和DUBBO Provider都会用到,主要功能是读取Apollo配置进行黑白名单的验证、处理请求IP、飞书告警相关工作

@Configuration
@Slf4j
public class BlockAllowListUtil {

    /**
     * Controller黑白名单开关(默认关闭)
     */
    private static Boolean BLOCK_ALLOW_LIST_CONTROLLER_CHECK = false;

    @Value("${block.allow.list.controller.check:}")
    private void setBlockAllowListControllerCheck(Boolean controllerCheck){
        BLOCK_ALLOW_LIST_CONTROLLER_CHECK = controllerCheck;
    }

    /**
     * Dubbo Provider黑白名单开关(默认关闭)
     */
    private static Boolean BLOCK_ALLOW_LIST_DUBBO_PROVIDER_CHECK = false;

    @Value("${block.allow.list.dubbo.provider.check:}")
    private void setBlockAllowListDubboProviderCheck(Boolean dubboProviderCheck){
        BLOCK_ALLOW_LIST_DUBBO_PROVIDER_CHECK = dubboProviderCheck;
    }

    /**
     * 飞书通知开关(默认关闭)
     */
    private static Boolean FEI_SHU_NOTICE = false;

    @Value("${block.allow.list.fei.shu.notice:}")
    private void setFeiShuNotice(Boolean feiShuNotice){
        FEI_SHU_NOTICE = feiShuNotice;
    }

    /**
     * 飞书机器人ID
     */
    private static String FEI_SHU_ROBOT_ID;

    @Value("${block.allow.list.fei.shu.robot.id:}")
    private void setFeiShuRobotId(String feiShuRobotId){
        FEI_SHU_ROBOT_ID = feiShuRobotId;
    }

    /**
     * 黑名单
     */
    private static String BLOCK_LIST;

    @Value("${block.list:}")
    private void setBlockList(String blockList){
        log.info("初始化 block_list :{}", blockList);
        BLOCK_LIST = blockList;
    }

    /**
     * 白名单
     */
    private static String ALLOW_LIST;

    @Value("${allow.list:}")
    private void setAllowList(String allowList){
        log.info("初始化 allow_list :{}", allowList);
        ALLOW_LIST = allowList;
    }

    /**
     * 获取Controller黑白名单开关
     * @return
     */
    public static Boolean getBlockAllowListControllerCheck() {
        return BLOCK_ALLOW_LIST_CONTROLLER_CHECK;
    }

    /**
     * 获取Dubbo Provider黑白名单开关
     * @return
     */
    public static Boolean getBlockAllowListDubboProviderCheck() {
        return BLOCK_ALLOW_LIST_DUBBO_PROVIDER_CHECK;
    }

    /**
     * 校验黑名单
     * @param requestIpAddress
     * @return true:不在黑名单内;false:在黑名单内拒绝;
     */
    public static Boolean checkBlockList(String interceptType, String requestIpAddress, String uri) {
        // 当没有配置黑名单时,返回false
        if(StringUtils.isBlank(BLOCK_LIST)) {
            // 没有配置黑名单,直接放行
            log.info("没有配置黑名单,直接放行");
            return true;
        }

        // 当黑名单内没有请求的IP,则放行
        if(!BLOCK_LIST.contains(requestIpAddress)) {
            return true;
        }

        // 当配置了飞书通知并且飞书机器人ID配置没问题
        if(FEI_SHU_NOTICE && StringUtils.isNotBlank(FEI_SHU_ROBOT_ID)) {
            // 当开启了通知
            String noticeMessage = initNoticeMessage(interceptType, "黑名单", requestIpAddress, uri);
            // FeishuUtil 飞书告警是另一个组件,参照官方文档封装一个即可
            Result noticeResult = FeishuUtil.sendSimpleMessage(FEI_SHU_ROBOT_ID, noticeMessage);
            if(!"00000".equals(noticeResult.getCode())) {
                log.error("黑名单拦截,发送飞书通知失败:{}", noticeResult.getMessage());
            }
        }

        return false;
    }

    /**
     * 校验白名单
     * @param requestIpAddress
     * @return true:不在白名单内;false:在白名单内拒绝;
     */
    public static Boolean checkAllowList(String interceptType, String requestIpAddress, String uri) {
        // 当没有配置白名单时,返回false
        if(StringUtils.isBlank(ALLOW_LIST)) {
            log.info("没有配置白名单,直接放行");
            // 没有配置白名单,直接放行
            return true;
        }

        // 当白名单内有请求的IP,则放行
        if(ALLOW_LIST.contains(requestIpAddress)) {
            return true;
        }

        // 当配置了飞书通知并且飞书机器人ID配置没问题
        if(FEI_SHU_NOTICE && StringUtils.isNotBlank(FEI_SHU_ROBOT_ID)) {
            // 当开启了通知
            String noticeMessage = initNoticeMessage(interceptType, "白名单", requestIpAddress, uri);
            // FeishuUtil 飞书告警是另一个组件,参照官方文档封装一个即可
            Result noticeResult = FeishuUtil.sendSimpleMessage(FEI_SHU_ROBOT_ID, noticeMessage);
            if(!"00000".equals(noticeResult.getCode())) {
                log.error("白名单拦截,发送飞书通知失败:{}", noticeResult.getMessage());
            }
        }

        return false;
    }

    /**
     * 组装飞书通知信息
     * @param listType
     * @param requestIpAddress
     * @param uri
     * @return
     */
    private static String initNoticeMessage(String interceptType, String listType, String requestIpAddress, String uri) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(interceptType);
        stringBuffer.append("拦截 - IP【");
        stringBuffer.append(requestIpAddress);
        stringBuffer.append("】触发");
        stringBuffer.append(listType);
        stringBuffer.append("控制,请求URI【");
        stringBuffer.append(uri);
        stringBuffer.append("】");
        stringBuffer.append("被拒绝!!");
        return stringBuffer.toString();
    }
}

Controller 验证

  • RequestFilter
@Slf4j
public class RequestFilter extends OncePerRequestFilter {

    @Override
    public void destroy() {
        super.destroy();
        log.info("RequestFilter destroy");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        try {
            RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest);
            filterChain.doFilter(requestWrapper, httpServletResponse);
        } catch (Exception exception) {
            httpServletResponse.setCharacterEncoding("utf-8");
            httpServletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();
            writer.write(exception.toString());
        }
    }
}
  • RequestWrapper
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    public RequestWrapper(HttpServletRequest request) {
        super(request);

        Boolean blockAllowCheck = BlockAllowListUtil.getBlockAllowListControllerCheck();
        if(!blockAllowCheck) {
            // 当黑白名校验关闭状态时,直接return,不进行白黑白名单判断
            return;
        }

        // 请求当地址
        String uri = request.getRequestURI();

        // 请求人IP
        String requestIpAddress = getIpAddr(request);

        Boolean checkBLockListResult = BlockAllowListUtil.checkBlockList("Controller", requestIpAddress, uri);
        if(!checkBLockListResult) {
            log.error("请求IP:{}在黑名单内,拦截本次请求!uri:{}", requestIpAddress, uri);
            throw new RuntimeException("黑名单拦截请求");
        }

        Boolean checkAllowListResult = BlockAllowListUtil.checkAllowList("Controller", requestIpAddress, uri);
        if(!checkAllowListResult) {
            log.error("请求IP:{}不在白名单内,拦截本次请求!uri:{}", requestIpAddress, uri);
            throw new RuntimeException("白名单拦截请求");
        }

    }

    @Override
    public String getParameter(String name) {
        // 可以对请求参数进行过滤
        return super.getParameter(name);
    }

    @Override
    public String[] getParameterValues(String name) {
        // 对请求参数值进行过滤
        String[] values =super.getRequest().getParameterValues(name);
        return super.getParameterValues(name);
    }

    /**
     * 获取用户真实ip方法
     * @param request
     * @return
     */
    public String getIpAddr(HttpServletRequest request) {

        String ip = request.getHeader("x - forwarded - for");
        if (ip == null || ip.length() == 0 ||"unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy - Client - IP");
        }

        if (ip == null || ip.length() == 0 ||"unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL - Proxy - Client - IP");
        }

        if (ip == null || ip.length() == 0 ||"unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }
        return ip;
    }

}
  • FilterConfig
@Configuration
public class FilterConfig {

    @Bean
    public RequestFilter requestFilter(){
        return new RequestFilter();
    }

    @Bean
    public FilterRegistrationBean<RequestFilter> registrationBean() {
        FilterRegistrationBean<RequestFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(requestFilter());
        // 拦截所有目录,这里可以通过Apollo配置,获取指定拦截路径
        registrationBean.addUrlPatterns("/*");
        // 设置请求过滤
        registrationBean.setName("RequestFilter");
        //过滤器的级别,值越小级别越高越先执行
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

DUBBO Provider 验证

增加一个DUBBO Provider过滤器即可

@Slf4j
@Activate(group = Constants.PROVIDER)
public class DubboProviderFilter implements Filter, Runnable {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

        Boolean blockAllowListCheck = BlockAllowListUtil.getBlockAllowListDubboProviderCheck();
        if(!blockAllowListCheck) {
            // 当黑白名校验关闭状态时,直接return,不进行白黑白名单判断
            return invoker.invoke(invocation);
        }

        // 获取执行方法名
        Result result = null;
        Long takeTime = 0L;
        try {
            // IP
            String requestIpAddress = RpcContext.getContext().getRemoteHost();
            // 应用
            String applicationName = RpcContext.getContext().getRemoteApplicationName();
            // 方法
            String method = invoker.getInterface() + "(" + invocation.getMethodName() + ")";
            // 参数
//            String parameters = Arrays.toString(invocation.getArguments());
            //执行方法
            result = invoker.invoke(invocation);

            if (result.getException() instanceof Exception) {
                throw new RpcException(result.getException());
            }

            String uri = applicationName+"/"+method;

            Boolean checkBLockListResult = BlockAllowListUtil.checkBlockList("DUBBO Provider", requestIpAddress, uri);
            if(!checkBLockListResult) {
                log.error("请求IP:{}在黑名单内,拦截本次请求!uri:{}", requestIpAddress, uri);
                throw new RpcException("黑名单拦截请求");
            }

            Boolean checkAllowListResult = BlockAllowListUtil.checkAllowList("DUBBO Provider", requestIpAddress, uri);
            if(!checkAllowListResult) {
                log.error("请求IP:{}不在白名单内,拦截本次请求!uri:{}", requestIpAddress, uri);
                throw new RpcException("白名单拦截请求");
            }

        } catch (Exception e) {
            log.error("filter拦截器发生错误", e);
            result.setException(new RpcException(e.getMessage()));
            return result;
        }
        return result;
    }

    @Override
    public void run() {
    }
}
  • 配置到引用的DUBBO工程中(PROJECT/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter)
# 配置DUBBO Provider拦截器,为指定组件的地址
# 前面是key,后面是DUBBO Provider过滤器的完整类路径
dubboProviderFilter=com.block.allow.list.config.DubboProviderFilter

飞书通知

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值