Jascypt-spring-boot的源码解析

最近研究了jasypt-spring-boot的源码,它的原理很简单,首先对配置文件的敏感信息进行加密,然后拦截配置文件的加载过程,获取配置文件的信息,并对加过密的配置使用与加密相同的算法进行解密就可以了。加密和解密的算法so easy,不提也罢。关键是配置文件的拦截过程,这个才是重点。

第一步:

先把准备工作做好,首先在@Configuration类中使用配置三个Bean

@Bean

public StringEncryptor stringEncryptor() {

return new DefaultLazyEncryptor();

}

@Bean

public EncryptablePropertyDetector encryptablePropertyDetector() {

return new DefaultLazyPropertyDetector();

}

@Bean

public EncryptablePropertyResolver encryptablePropertyResolver(EncryptablePropertyDetector propertyDetector, StringEncryptor encryptor) {

return new DefaultLazyPropertyResolver(propertyDetector, encryptor);

}

说明:StringEncryptor会对加过密的配置进行解密。

EncryptablePropertyDetector会对加过密的配置进行去除前缀和后缀的操作。

EncryptablePropertyResolver会同时使用StringEncryptorEncrypatblePropertyDetector,会先对加过密的配置进行去除前缀和后缀的操作,然后对其进行解密操作。

例如:配置JIA(DuafkhAH47w=)EncryptablePropertyResolver会使用EncryptablePropertyDetector的某个方法判断该配置是否以”JIA(“开头并且以”)”结尾。如果是,就对该配置进行去除前缀和后缀的操作,得到”DuafkhAH47w=”,并对这个值进行解密操作。

第二步需要配置EnableEncryptablePropertiesBeanFactoryPostProcessor这个bean


EnableEncryptablePropertiesBeanFactoryPostProcessor这个类会使用第一步配置好的EncryptablePropertyResolver对拦截到的配置进行解密,并把解密的结果替换掉解密之前的值。这一步的核心是如何对配置进行拦截。

这个类的源码如下:

public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationListener<ApplicationEvent>, Ordered{

	private static final Logger log = LoggerFactory.getLogger(EnableEncryptablePropertiesBeanFactoryPostProcessor.class);
	
	private ConfigurableEnvironment environment;
    private InterceptionMode interceptionMode;
	
	public EnableEncryptablePropertiesBeanFactoryPostProcessor(ConfigurableEnvironment environment,
			InterceptionMode interceptionMode) {
		this.environment = environment;
		this.interceptionMode = interceptionMode;
	}

	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	/**
	 * ApplicationEvent event是所要监听的对象
	 */
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		log.debug("Application Event Raised: {}", event.getClass().getSimpleName());
	}

	/**
	 * 被类被spring容器加载的时候,该方法会执行
	 */
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		log.info("Post-processing PropertySource instances");
		/**
		 * 使用EncryptablePropertyResolver对象来对加过密的配置进行解密。
		 */
		EncryptablePropertyResolver propertyResolver = beanFactory.getBean(EncryptablePropertyResolver.class);
        MutablePropertySources propSources = environment.getPropertySources();
        EncryptablePropertySourceConverter.convertPropertySources(interceptionMode, propertyResolver, propSources);
	}

}

由于的这个过程过于复杂,所以实现了BeanFactoryPostProcessor接口,把具体的实现委托给了EncryptablePropertySourceConverter这个类来处理了。

这个类的源码如下:

public class EncryptablePropertySourceConverter {
	private static final Logger log = LoggerFactory.getLogger(EncryptablePropertySourceConverter.class);
	
	public static void convertPropertySources(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, MutablePropertySources propSources) {
        StreamSupport.stream(propSources.spliterator(), false)
        		/*过滤掉EncryptablePropertySource类型的MutablePropertySources*/
                .filter(ps -> !(ps instanceof EncryptablePropertySource))
                .map(ps -> makeEncryptable(interceptionMode, propertyResolver, ps))
                .collect(Collectors.toList())
                .forEach(ps -> propSources.replace(ps.getName(), ps));
    }

    @SuppressWarnings("unchecked")
    public static <T> PropertySource<T> makeEncryptable(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, PropertySource<T> propertySource) {
        //又检查了一次
    	if (propertySource instanceof EncryptablePropertySource) {
            return propertySource;
        }
        PropertySource<T> encryptablePropertySource = convertPropertySource(interceptionMode, propertyResolver, propertySource);
        log.info("Converting PropertySource {} [{}] to {}", propertySource.getName(), propertySource.getClass().getName(),
                AopUtils.isAopProxy(encryptablePropertySource) ? "AOP Proxy" : encryptablePropertySource.getClass().getSimpleName());
        return encryptablePropertySource;
    }

    /**
     * 使用拦截器对属性值进行拦截,并对属性值进行解密操作。
     * @param interceptionMode
     * @param propertyResolver
     * @param propertySource
     * @return
     */
    private static <T> PropertySource<T> convertPropertySource(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, PropertySource<T> propertySource) {
        return interceptionMode == InterceptionMode.PROXY
                ? proxyPropertySource(propertySource, propertyResolver) : instantiatePropertySource(propertySource, propertyResolver);
    }

    /**
     * 使用了cglib代理对属性源进行了拦截和机密
     * @param propertySource
     * @param resolver
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> PropertySource<T> proxyPropertySource(PropertySource<T> propertySource, EncryptablePropertyResolver resolver) {
        //Silly Chris Beams for making CommandLinePropertySource getProperty and containsProperty methods final. Those methods
        //can't be proxied with CGLib because of it. So fallback to wrapper for Command Line Arguments only.
        if (CommandLinePropertySource.class.isAssignableFrom(propertySource.getClass())) {
            return instantiatePropertySource(propertySource, resolver);
        }
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTargetClass(propertySource.getClass());
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.addInterface(EncryptablePropertySource.class);
        proxyFactory.setTarget(propertySource);
        proxyFactory.addAdvice(new EncryptablePropertySourceMethodInterceptor<>(propertySource, resolver));
        return (PropertySource<T>) proxyFactory.getProxy();
    }

    /**
     * PropertySource的名字匹配
     * "org.springframework.boot.context.config.ConfigFileApplicationListener$ConfigurationPropertySources",
     * "org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource"
     * 当中的一个,就使用cglib动态代理的方式,对属性值进行拦截和解密。
     * 
     * 如果不匹配,就使用各种类型的PropertySourceWrapper对不同类型的属性源进行包装解密。(使用包装器来对属性进行解密)
     * @param propertySource
     * @param resolver
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> PropertySource<T> instantiatePropertySource(PropertySource<T> propertySource, EncryptablePropertyResolver resolver) {
        PropertySource<T> encryptablePropertySource;
        if (needsProxyAnyway(propertySource)) {
            encryptablePropertySource = proxyPropertySource(propertySource, resolver);
        } else if (propertySource instanceof MapPropertySource) {
            encryptablePropertySource = (PropertySource<T>) new EncryptableMapPropertySourceWrapper((MapPropertySource) propertySource, resolver);
        } else if (propertySource instanceof EnumerablePropertySource) {
            encryptablePropertySource = new EncryptableEnumerablePropertySourceWrapper<>((EnumerablePropertySource) propertySource, resolver);
        } else {
            encryptablePropertySource = new EncryptablePropertySourceWrapper<>(propertySource, resolver);
        }
        return encryptablePropertySource;
    }

    @SuppressWarnings("unchecked")
    private static boolean needsProxyAnyway(PropertySource<?> ps) {
        return needsProxyAnyway((Class<? extends PropertySource<?>>) ps.getClass());
    }

    private static boolean needsProxyAnyway(Class<? extends PropertySource<?>> psClass) {
        return needsProxyAnyway(psClass.getName());
    }

    /**
     *  Some Spring Boot code actually casts property sources to this specific type so must be proxied.
     */
    private static boolean needsProxyAnyway(String className) {
        return Stream.of(
                "org.springframework.boot.context.config.ConfigFileApplicationListener$ConfigurationPropertySources",
                "org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource"
                ).anyMatch(className::equals);
    }
}

上面的处理逻辑是这样的:如果使用的是代理的模式,就使用spring封装的cglib代理类来对属性值进行解密处理。如果使用的是包装器的模式,就分别对不同类型的属性源使用不同的包装器对它们进行解密操作。

在微服务的开发模式下,一般不使用上面介绍的方式对配置进行加密处理,一般会使用配置中心,来集中对所有的配置进行处理,然后各个服务的项目通过http或其他方式来请求配置中心的配置。

下一周我会把的这个问题解决并汇总,写出博客。

又想相互交流相互学习的同学,可以加这个qq群:313145288

猜你喜欢

转载自blog.csdn.net/jiaweicheng77/article/details/80465532