[JAVA] 拦截Druid数据源自动注入帐密解密实现详解

1930 0
黑夜隐士 2022-11-6 11:17:25 | 显示全部楼层 |阅读模式
目录

    背景
      加密数据源自主实现流程基础巩固额外尝试启示录



背景

SpringBoot 项目,使用 Druid 自动装配的数据源,数据源的帐号密码配置加密后,如何完成数据源的装配呢?
druid-spring-boot-starter 虽然自带了加密配置,但是密钥也是配置的,如果需要用自定义的加密解密工具,如果不用自带的工具,怎么自定义实现加密数据源的装配呢?
本文从 DruidDataSourceAutoConfigure 类源码入手,仿造该类,自定义一个数据源注入配置,在真正注入 DruidDataSource 之前,对 druid 配置信息完成解密。
主要思考三个问题:
    自定的 Configuration 类中的 @Bean 注入一个 DruidDataSource ,为什么比自动装配的时机早呢?如果自定义一个自动装配类, 包含DataSourceProperties 属性,对它的帐号密码解密后,让它在DruidDataSourceAutoConfigure 类之前装配,怎么实现呢?自动装配类的工作原理是什么?注入优先级怎么确定的?

加密数据源自主实现流程
  1. Not registered via @EnableConfigurationProperties,
  2. marked as Spring component,
  3. or scanned via @ConfigurationPropertiesScan
复制代码
@ConfigurationProperties 用法限制,我想到一个解决办法,为当前类加上 @Component,同时制定一个不可能的注入条件:@ConditionalOnProperty(prefix = "xx",name = "xxx", havingValue = "impossible")。
不用官方的加密插件,自定义 Druid 的解密配置,我想到的方法是完全仿照 Druid 数据源的自动装配过程,改写 DruidDataSource 的注入过程。
关键是修改 DataSourceProperties 这个类的实例的帐号密码属性,其他完全照搬 DruidDataSourceAutoConfigure 实现即可。
第一步,由于 Druid 自动注入的数据源 DruidDataSourceWrapper 是一个包内类,不能直接拿来用,所以完全拷贝一份这个类,定义为咱们自己的数据源类:
  1. @Component
  2. @ConfigurationProperties("spring.datasource.druid")
  3. @ConditionalOnProperty(prefix = "spring.datasource",name = "encrypted", havingValue = "impossible")
  4. public class MyEncryptedDatasourceWrapper extends DruidDataSource implements InitializingBean {
  5.     @Autowired
  6.     private DataSourceProperties basicProperties;
  7.     public MyEncryptedDatasourceWrapper() {
  8.     }
  9.     @Override
  10.     public void afterPropertiesSet() {
  11.         if (super.getUsername() == null) {
  12.             super.setUsername(this.basicProperties.determineUsername());
  13.         }
  14.         if (super.getPassword() == null) {
  15.             super.setPassword(this.basicProperties.determinePassword());
  16.         }
  17.         if (super.getUrl() == null) {
  18.             super.setUrl(this.basicProperties.determineUrl());
  19.         }
  20.         if (super.getDriverClassName() == null) {
  21.             super.setDriverClassName(this.basicProperties.getDriverClassName());
  22.         }
  23.     }
  24.     @Autowired(
  25.             required = false
  26.     )
  27.     public void autoAddFilters(List<Filter> filters) {
  28.         super.filters.addAll(filters);
  29.     }
  30.     @Override
  31.     public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) {
  32.         try {
  33.             super.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
  34.         } catch (IllegalArgumentException var4) {
  35.             super.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;
  36.         }
  37.     }
  38. }
复制代码
第二步,自定义一个 DruidDataSourceAutoConfigure 类,内容与该类一样,但是多一个数据源配置属性:
  1. @Configuration
  2. @EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
  3. @Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
  4. public class MyEncryptedDatasourceWrapperConfig {
  5.     /**
  6.      * 该属性封装了 spring.datasource 属性,需要对它的帐号、密码属性进行解密
  7.      */
  8.     @Autowired
  9.     private DataSourceProperties basicProperties;
  10.     /**
  11.      * 使用数据源配置信息,解密帐号和密码后创建数据库连接池
  12.      * @return
  13.      */
  14.     @Bean
  15.     public DataSource dataSource() {
  16.         // TODO 对密码解密并设置回去
  17.         basicProperties.setPassword(password);
  18.         return new MyEncryptedDatasourceWrapper();
  19.     }
  20. }
复制代码
这样就完成了 Spring druid 数据源配置的解密处理了。

基础巩固

boolean proxyBeanMethods() 默认值是 true. 从这个成员变量的注释中,我们可以看到一句话 Specify whether {@code @Bean} methods should get proxied in order to enforce bean lifecycle behavior, e.g. to return shared singleton bean instances even in case of direct {@code @Bean} method calls in user code.
其实从这句话,我们就可以初步得到我们想要的答案了:在带有 @Configuration 注解的类中,一个带有 @Bean 注解的方法显式调用另一个带有 @Bean 注解的方法,返回的是共享的单例对象。
参考文档:《Component 和 Configuration 区别》

额外尝试

加密数据源配置的解密流程,核心在 DataSourceProperties 这个实例装配完成后修改密码信息,尝试自定义一个 @Configuration 中 @Bean 注入一个 DataSourceProperties 实例,但是这个对象到了 Druid 自动注入类那里,属性还是没有发生变化:
  1. @Configuration
  2. @EnableConfigurationProperties(DataSourceProperties.class)
  3. public class MyAutoConfig {
  4.     @Autowired
  5.     private DataSourceProperties basicProperties;
  6.     public MyAutoConfig() {
  7.         System.out.println("My Auto config");
  8.     }
  9.     @Bean
  10.     public DataSourceProperties basicProperties() {
  11.         // TODO 解密配置
  12.         System.out.println("username " + basicProperties.getUsername());
  13.         return basicProperties;
  14.     }
  15. }
复制代码
这里 @Bean 注入生效了,到了 DruidDataSource 那使用的也是这个实例,单步调试对象地址是一样的,但是改的属性没有生效。


数据源实例化配置时引用的属性:


但是两个地方的属性却不同,前一步解密的信息并没有传递到真正使用的地方。 理论上,同一个对象,前面修改了属性,这里同一个线程里面,属性应该变化了才对呢!不得其解。

启示录

回顾开头的三个问题:
    自定的 Configuration 类中的 @Bean 注入一个 DruidDataSource ,为什么比自动装配的时机早呢?因为 @Bean 属于当前项目扫描路径,它里面的类注入优先级高于第三方 jar 包中的 spring.factories 的装配类。如果自定义一个自动装配类, 包含DataSourceProperties 属性,对它的帐号密码解密后,让它在DruidDataSourceAutoConfigure 类之前装配,怎么实现呢?尝试定义一个装配类 @Bean 装配一个数据源 DataSourceProperties 对象,并修改配置。自动装配类的工作原理是什么?注入优先级怎么确定的?spring.factories 的本质是 SPI,它针对的是第三方 jar 包,不需要手动配置扫描路径,又需要自动注入的情况,是各种 starter 定义底层实现途径。
优先级:本地扫描路径的 Configuration -> 实现了 BeanDefinitionRegistryPostProcessor 接口的类 -> spring.factories 中的自动装配类。
以上就是拦截Druid数据源自动注入帐密解密实现详解的详细内容,更多关于Druid注入帐密解密拦截的资料请关注中国红客联盟其它相关文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行