Jasypt的基本原理与其在 spring 中的工作机制分析

JASYPT SPRING SPRINGBOOT

Posted by gomyck on August 7, 2020

通过源码解读 jasypt, 查看其工作机制

jasypt-spring-boot-starter-x.x.x.jar

jasypt 对于 springboot 的支持包, 查看包内类文件:

Dependency management is a critical aspects of any complex project. And doing this manually is less than ideal; the more time you spent on it the less time you have on the other important aspects of the project.

1
2
3
4
5
|-- com.ulisesbocchio.jasyptspringboot
    - JasyptSpringBootAutoConfiguration
    - JasyptSpringCloudBootstrapConfiguration
|-- META-INF
    - spring.factories

1. 查看 spring.factories 文件, 分别配置了 springboot 的自动配置与 springCloud 的自动配置(springcloud 与 springboot 上下文分别初始化, 为父子关系)

1
2
3
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringCloudBootstrapConfiguration

2. 两个类中引入了相同的配置类: @Import({EnableEncryptablePropertiesConfiguration.class})

这个类为 com.ulisesbocchio.jasyptspringboot 包中的类, 是 jasypt 对于 springboot 封装的一些处理类

jasypt 还有核心的包: org.jasypt, 这个包定义了顶级接口以及常用的密码术逻辑

3. 查看 EnableEncryptablePropertiesConfiguration 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})
@Slf4j
public class EnableEncryptablePropertiesConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Bean
    public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(final ConfigurableEnvironment environment) {
        // 获取配置文件中的配置信息, 是否要代理 propertySource 对象, jasypt 对 ps 对象有两种处理方式, 下面会说. 如果没有, 那么默认为 false
        final boolean proxyPropertySources = environment.getProperty("jasypt.encryptor.proxyPropertySources", Boolean.TYPE, false);
        // 根据上一步获取的配置, 选择代理模式
        final InterceptionMode interceptionMode = proxyPropertySources ? InterceptionMode.PROXY : InterceptionMode.WRAPPER;
        // 创建 beanFactoryPostProcessor
        return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, interceptionMode);
    }

    @Override
    public void initialize(final ConfigurableApplicationContext applicationContext) {
        log.info("Bootstraping jasypt-string-boot auto configuration in context: {}", applicationContext.getId());
    }
}

4. 查看上一步 import 的 EncryptablePropertyResolverConfiguration类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
@Configuration
public class EncryptablePropertyResolverConfiguration {

    private static final String ENCRYPTOR_BEAN_PLACEHOLDER = "${jasypt.encryptor.bean:jasyptStringEncryptor}";
    private static final String DETECTOR_BEAN_PLACEHOLDER = "${jasypt.encryptor.property.detector-bean:encryptablePropertyDetector}";
    private static final String RESOLVER_BEAN_PLACEHOLDER = "${jasypt.encryptor.property.resolver-bean:encryptablePropertyResolver}";
    private static final String FILTER_BEAN_PLACEHOLDER = "${jasypt.encryptor.property.filter-bean:encryptablePropertyFilter}";

    private static final String ENCRYPTOR_BEAN_NAME = "lazyJasyptStringEncryptor";
    private static final String DETECTOR_BEAN_NAME = "lazyEncryptablePropertyDetector";
    private static final String CONFIG_SINGLETON = "configPropsSingleton";
    static final String RESOLVER_BEAN_NAME = "lazyEncryptablePropertyResolver";
    static final String FILTER_BEAN_NAME = "lazyEncryptablePropertyFilter";

    // spring 环境副本
    @Bean
    public EnvCopy envCopy(final ConfigurableEnvironment environment) {
        return new EnvCopy(environment);
    }
    // 字符串解密器, 用于解密加密的字符串, 属性解析器里会用到
    @Bean(name = ENCRYPTOR_BEAN_NAME)
    public StringEncryptor stringEncryptor(
                @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") final EnvCopy envCopy,
                final BeanFactory bf) {
        // 获取 string 解密器的名称, 如果没有 使用默认的名字: jasyptStringEncryptor
        final String customEncryptorBeanName = envCopy.get().resolveRequiredPlaceholders(ENCRYPTOR_BEAN_PLACEHOLDER);
        return new DefaultLazyEncryptor(envCopy.get(), customEncryptorBeanName, bf);
    }
    // 可解密属性探测器, 用于发现配置文件中需要被解密的 value
    @Bean(name = DETECTOR_BEAN_NAME)
    public EncryptablePropertyDetector encryptablePropertyDetector(
                @SuppressWarnings({"SpringJavaInjectionPointsAutowiringInspection"}) final EnvCopy envCopy,
                final BeanFactory bf) {
        // 获取前缀
        final String prefix = envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.prefix:ENC(}");
        // 获取后缀
        final String suffix = envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.suffix:)}");
        // 获取探测器的 bean 名称, 使用resolveRequiredPlaceholders方法可以获得配置文件中的属性值, 如果没有, 使用默认值: encryptablePropertyDetector
        final String customDetectorBeanName = envCopy.get().resolveRequiredPlaceholders(DETECTOR_BEAN_PLACEHOLDER);
        return new DefaultLazyPropertyDetector(prefix, suffix, customDetectorBeanName, bf);
    }
    // jasypt 配置 bean  因为此时配置文件还不能直接使用(有需要被解密的值)  所以不要使用 spring 来注入, 而是从上下文中获取配置, 然后使用 spring 的工具手动的绑定到配置类上
    @Bean(name = CONFIG_SINGLETON)
    public Singleton<JasyptEncryptorConfigurationProperties> configProps(
                @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") final EnvCopy envCopy,
                final ConfigurableBeanFactory bf) {
        return new Singleton<>(() -> {
            // 绑定处理器  忽略错误的绑定处理器
            final BindHandler handler = new IgnoreErrorsBindHandler(BindHandler.DEFAULT);
            // 配置文件
            final MutablePropertySources propertySources = envCopy.get().getPropertySources();
            // 绑定器
            final Binder binder = new Binder(ConfigurationPropertySources.from(propertySources),
                        new PropertySourcesPlaceholdersResolver(propertySources),
                        ApplicationConversionService.getSharedInstance(), bf::copyRegisteredEditorsTo);
            // jasypt 配置信息
            final JasyptEncryptorConfigurationProperties config = new JasyptEncryptorConfigurationProperties();
            // 解析类型, 记录了需要解析的 class 一切元信息, 是个很复杂的类, 包含了一个类型的所有 reflect 信息 以及常用的判断函数
            final ResolvableType type = ResolvableType.forClass(JasyptEncryptorConfigurationProperties.class);
            // 寻找ConfigurationProperties注解信息
            final Annotation annotation = AnnotationUtils.findAnnotation(JasyptEncryptorConfigurationProperties.class,
                        ConfigurationProperties.class);
            final Annotation[] annotations = new Annotation[] {annotation};
            // 声明一个可绑定类型
            final Bindable<?> target = Bindable.of(type).withExistingValue(config).withAnnotations(annotations);
            // 开始绑定
            binder.bind("jasypt.encryptor", target, handler);
            return config;
        });
    }
    // 声明一个  属性过滤器
    @SuppressWarnings("unchecked")
    @Bean(name = FILTER_BEAN_NAME)
    public EncryptablePropertyFilter encryptablePropertyFilter(
                @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") final EnvCopy envCopy,
                final ConfigurableBeanFactory bf,
                @Qualifier(CONFIG_SINGLETON) final Singleton<JasyptEncryptorConfigurationProperties> configProps) {
        final String customFilterBeanName = envCopy.get().resolveRequiredPlaceholders(FILTER_BEAN_PLACEHOLDER);
        // 声明过滤器, 指定包含的资源, 不包含的资源, 包含的属性名, 不包含的属性名
        final FilterConfigurationProperties filterConfig = configProps.get().getProperty().getFilter();
        return new DefaultLazyPropertyFilter(filterConfig.getIncludeSources(), filterConfig.getExcludeSources(),
                    filterConfig.getIncludeNames(), filterConfig.getExcludeNames(), customFilterBeanName, bf);
    }
    // 核心  属性解析器
    @Bean(name = RESOLVER_BEAN_NAME)
    public EncryptablePropertyResolver encryptablePropertyResolver(
                @Qualifier(DETECTOR_BEAN_NAME) final EncryptablePropertyDetector propertyDetector,
                @Qualifier(ENCRYPTOR_BEAN_NAME) final StringEncryptor encryptor, final BeanFactory bf,
                @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") final EnvCopy envCopy) {
        final String customResolverBeanName = envCopy.get().resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER);
        return new DefaultLazyPropertyResolver(propertyDetector, encryptor, customResolverBeanName, bf);
    }

    /**
     * Need a copy of the environment without the Enhanced property sources to avoid circular dependencies.
     */
    private static class EnvCopy {
        StandardEnvironment copy;

        EnvCopy(final ConfigurableEnvironment environment) {
            copy = new StandardEnvironment();
            environment.getPropertySources().forEach(ps -> {
                final PropertySource<?> original = ps instanceof EncryptablePropertySource
                            ? ((EncryptablePropertySource) ps).getDelegate()
                            : ps;
                copy.getPropertySources().addLast(original);
            });
        }

        ConfigurableEnvironment get() {
            return copy;
        }
    }

}

5. 查看 第 3 步中声明的 EnableEncryptablePropertiesBeanFactoryPostProcessor (核心类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        LOG.info("Post-processing PropertySource instances");
        // 获取第 4 步声明的 属性解析器
        EncryptablePropertyResolver propertyResolver = beanFactory.getBean(RESOLVER_BEAN_NAME, EncryptablePropertyResolver.class);
        // 获取第 4 步声明的 过滤器
        EncryptablePropertyFilter propertyFilter = beanFactory.getBean(FILTER_BEAN_NAME, EncryptablePropertyFilter.class);
        // 从上下文中拿到配置信息资源
        MutablePropertySources propSources = environment.getPropertySources();
        // 开始转换资源信息, 这个函数是 EncryptablePropertySourceConverter 类的静态方法, 在文件头部 import 了
        convertPropertySources(interceptionMode, propertyResolver, propertyFilter, propSources);
    }

    ...省略的一些代码

}

6. 查看 EncryptablePropertySourceConverter (核心类 真正的解密转换处理类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public class EncryptablePropertySourceConverter {

    // 第 5 步调用的静态方法
    public static void convertPropertySources(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, EncryptablePropertyFilter propertyFilter, MutablePropertySources propSources) {
        StreamSupport.stream(propSources.spliterator(), false)
                .filter(ps -> !(ps instanceof EncryptablePropertySource)) // 过滤出不是可解密的属性资源对象
                .map(ps -> makeEncryptable(interceptionMode, propertyResolver, propertyFilter, ps)) // 把资源对象转换成 EncryptablePropertySource 对象
                .collect(toList()) // 组成集合
                .forEach(ps -> propSources.replace(ps.getName(), ps)); // 替换属性对象
    }

    // 生成可解密对象
    @SuppressWarnings("unchecked")
    public static <T> PropertySource<T> makeEncryptable(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, EncryptablePropertyFilter propertyFilter, PropertySource<T> propertySource) {
        if (propertySource instanceof EncryptablePropertySource) {
            return propertySource;
        }
        // 转换方法 最最最最核心的代码了
        PropertySource<T> encryptablePropertySource = convertPropertySource(interceptionMode, propertyResolver, propertyFilter, propertySource);
        log.info("Converting PropertySource {} [{}] to {}", propertySource.getName(), propertySource.getClass().getName(),
                AopUtils.isAopProxy(encryptablePropertySource) ? "AOP Proxy" : encryptablePropertySource.getClass().getSimpleName());
        return encryptablePropertySource;
    }

    // 转换当前 propertySource 对象为可解密对象
    private static <T> PropertySource<T> convertPropertySource(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, EncryptablePropertyFilter propertyFilter, PropertySource<T> propertySource) {
        return interceptionMode == InterceptionMode.PROXY
                ? proxyPropertySource(propertySource, propertyResolver, propertyFilter) : instantiatePropertySource(propertySource, propertyResolver, propertyFilter);
    }
    // 对外提供的公有方法, 本类中没用
    public static MutablePropertySources proxyPropertySources(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, EncryptablePropertyFilter propertyFilter, MutablePropertySources propertySources) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(MutablePropertySources.class);
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.addInterface(PropertySources.class);
        proxyFactory.setTarget(propertySources);
        proxyFactory.addAdvice(new EncryptableMutablePropertySourcesInterceptor(interceptionMode, propertyResolver, propertyFilter));
        return (MutablePropertySources) proxyFactory.getProxy();
    }
    // 代理属性对象
    @SuppressWarnings("unchecked")
    public static <T> PropertySource<T> proxyPropertySource(PropertySource<T> propertySource, EncryptablePropertyResolver resolver, EncryptablePropertyFilter propertyFilter) {
        //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.

        // 如果是 CommandLinePropertySource 类型 或者 这个类是 final 的, 那么就不做代理了, 使用 wrapper 代替 ps
        if (CommandLinePropertySource.class.isAssignableFrom(propertySource.getClass())
            // Other PropertySource classes like org.springframework.boot.env.OriginTrackedMapPropertySource
            // are final classes as well
            || Modifier.isFinal(propertySource.getClass().getModifiers())) {
            return instantiatePropertySource(propertySource, resolver, propertyFilter);
        }
        // 代理开始
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTargetClass(propertySource.getClass());
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.addInterface(EncryptablePropertySource.class);
        proxyFactory.setTarget(propertySource);
        proxyFactory.addAdvice(new EncryptablePropertySourceMethodInterceptor<>(propertySource, resolver, propertyFilter));
        return (PropertySource<T>) proxyFactory.getProxy();
    }

    @SuppressWarnings("unchecked")
    public static <T> PropertySource<T> instantiatePropertySource(PropertySource<T> propertySource, EncryptablePropertyResolver resolver, EncryptablePropertyFilter propertyFilter) {
        PropertySource<T> encryptablePropertySource;
        if (needsProxyAnyway(propertySource)) { // 判断 如果是指定的类型, 那么就走代理
            encryptablePropertySource = proxyPropertySource(propertySource, resolver, propertyFilter);
        } else if (propertySource instanceof MapPropertySource) {
            encryptablePropertySource = (PropertySource<T>) new EncryptableMapPropertySourceWrapper((MapPropertySource) propertySource, resolver, propertyFilter);
        } else if (propertySource instanceof EnumerablePropertySource) {
            encryptablePropertySource = new EncryptableEnumerablePropertySourceWrapper<>((EnumerablePropertySource) propertySource, resolver, propertyFilter);
        } else {
            encryptablePropertySource = new EncryptablePropertySourceWrapper<>(propertySource, resolver, propertyFilter);
        }
        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);
    }
}

7. 至此 jasypt 的核心逻辑分析结束

后续在需要使用 propertySource 的时候, 实际上调用的是 proxy 对象的 getProperty 的方法, 或者 wrapper 的 getProperty 方法

其中使用的核心类为 CachingDelegateEncryptablePropertySource , 这个类继承了 propertySource, 并实现了 EncryptablePropertySource 接口

内部维护了一个 map 缓存, 初始化时, 需要使用 属性解析器 以及 过滤器, 然后使用 解密器解密, 过滤器进行属性过滤

8. 其他类

除了针对配置文件的处理, 还有针对注解的处理: EncryptablePropertySourceBeanFactoryPostProcessor

这个类, 与 EnableEncryptablePropertiesBeanFactoryPostProcessor 类似, 只不过没有实现 applicationListener

它在 postProcessBeanFactory 方法之后, 扫描了注释了 EncryptablePropertySources 的 bean, 并把注解中的配置, 初始化成 ps 对象, 放到上下文中

9. 思路整理:

入口(JasyptSpringBootAutoConfiguration JasyptSpringCloudBootstrapConfiguration)

配置类(EnableEncryptablePropertiesConfiguration), 注册 Bean 类型为: beanFactoryPostProcessor, 在初始化之后调用一些方法

配置类引入了一个属性解析配置类(EncryptablePropertyResolverConfiguration), 类内声明了 字符串解密器(StringEncryptor), 环境上下文副本(EnvCopy), 属性解析器(EncryptablePropertyResolver), 属性过滤器(EncryptablePropertyFilter)

定义的 beanFactoryPostProcessor 在回调中获取: 属性解析器, 属性过滤器, 环境上下文, 然后开始代理属性对象, 后续 spring 调用 propertySource 的 getProperty 实际调用 jasypt 代理的属性对象, 从而获得解密加密能力