Skip to content

卷起来🐎🐲💪 -- 服务治理(统一白名单控制)

Posted on:May 30, 2023 at 09:00 PM
Share on

Table of contents

Open Table of contents

需求背景

在互联网这种多数面向C端用户场景下的产品功能研发完成交付后,通常并不会直接发布上线。尤其是在一个原有服务功能已经沉淀了大量用户时,不断的迭代开发新增需求下,更不会贸然发布上线。

虽然在测试环境、预发环境都有了相应功能的验证,但在真实的用户场景下可能还会存在其他隐患问题。那么为了更好的控制系统风险,通常需要研发人员在代码的接口层,提供白名单控制。上线初期先提供可配置的白名单用户进行访问验证,控制整体的交付风险程度。

白名单确实可以解决接口功能或者服务入口的访问范围风险,那么这里有一个技术方案实现问题。就是如果研发人员在所有的接口上都加这样的白名单功能,那么就会非常耗费精力,同时在功能不再需要时可能还需要将代码删除。在这个大量添加和修改重复功能的代码过程中,也在一定程度上造成了研发成本和操作风险。所以站在整体的系统建设角度来说,我们需要有一个通用的白名单服务系统,减少研发在这方面的重复开发。

方案设计

白名单服务属于业务系统开发过程中可重复使用的通用功能,所以我们可以把这样的工具型功能单独提炼出来设计成技术组件,由各个需要的使用此功能的系统工程引入使用。整体的设计方案如图:

技术实现

白名单中间件类关系

白名单控制中间件整个实现工程并不复杂,其核心点在于对切面的理解和运用,以及一些配置项需要按照 SpringBoot 中的实现方式进行开发。

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoWhiteList {

    String key() default "";

    String returnJson() default "";

}

白名单配置获取

@ConfigurationProperties("zerotone.whitelist")
public class WhiteListProperties {

    private String users;

    public String getUsers() {
        return users;
    }

    public void setUsers(String users) {
        this.users = users;
    }
}
@Configuration
@ConditionalOnClass(WhiteListProperties.class)
@EnableConfigurationProperties(WhiteListProperties.class)
public class WhiteListAutoConfigure {

    @Bean("whiteListConfig")
    @ConditionalOnMissingBean
    public String whiteListConfig(WhiteListProperties properties) {
        return properties.getUsers();
    }

    // 手动Bean,这样其他的项目引用不需要扫描切面类,完成注册
    @Bean
    public DoJoinPoint doJoinPoint() {
        return new DoJoinPoint();
    }
}

切面逻辑实现

@Aspect
@Component
public class DoJoinPoint {
    private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);

    @Resource
    private String whiteListConfig;

    @Pointcut("@annotation(xyz.zerotone.middleware.whitelist.annotation.DoWhiteList)")
    public void aopPoint() {
    }

    @Around("aopPoint()")
    public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
        // 获取内容
        Method method = getMethod(jp);
        DoWhiteList whiteList = method.getAnnotation(DoWhiteList.class);

        // 获取字段值
        String keyValue = getFiledValue(whiteList.key(), jp.getArgs());
        logger.info("middleware whitelist handler method:{} value:{}", method.getName(), keyValue);
        if (null == keyValue || "".equals(keyValue)) return jp.proceed();

        String[] split = whiteListConfig.split(",");

        // 白名单过滤
        for (String str : split) {
            if (keyValue.equals(str)) {
                return jp.proceed();
            }
        }

        // 拦截
        return returnObject(whiteList, method);
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    // 返回对象
    private Object returnObject(DoWhiteList whiteList, Method method) throws IllegalAccessException, InstantiationException {
        Class<?> returnType = method.getReturnType();
        String returnJson = whiteList.returnJson();
        if ("".equals(returnJson)) {
            return returnType.newInstance();
        }
        return JSON.parseObject(returnJson, returnType);
    }

    // 获取属性值
    private String getFiledValue(String filed, Object[] args) {
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (null == filedValue || "".equals(filedValue)) {
                    filedValue = BeanUtils.getProperty(arg, filed);
                } else {
                    break;
                }
            } catch (Exception e) {
                if (args.length == 1) {
                    return args[0].toString();
                }
            }
        }
        return filedValue;
    }
}

所以这部分代码比较多,但整体的逻辑实现并不复杂,主要包括如下内容:

代码地址

源码地址

测试项目地址

Share on