• 欢迎访问ByWei.Cn,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站,加入百味博客 QQ群
  • 已升级为最新版主题,并将持续优化改造中,支持说说碎语功能,可像添加文章一样直接添加说说,博客主题升级啦
  • 感谢您百度求点赞啊!百度网址
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏百味博客吧
  • 博主热烈欢迎 软件定制开发 联系:http://www.bywei.cn

SpringBoot使用Druid数据库加密链接完整方案

数据库 bywei 1年前 (2018-10-25) 16492次浏览 7个评论 扫描二维码

Druid 介绍,Druid 链接加密

网上的坑

springboot 使用 Druid 数据库加密链接方案,不建议采用网上的一篇文章《springboot 结合 Druid 加密数据库密码遇到的坑!》介绍的方式来进行加密链接实现。以下方式兼容 SpringBoot1 和 SpringBoot2.0 及以上。本文章下文分享 Druid 源码后就知道为什么不建议采用该方式的原因了。

加密准备

首先使用 CMD 生成数据库加密字符串,该命令会产生三个值 privateKey=公钥、publicKey=密码加密后的结果、password=密码加密串

java -cp druid-1.0.28.jar com.alibaba.druid.filter.config.ConfigTools pcds123123456

使用 Durid 的工具类 ConfigTools 验证加密字符串

@Test
public void db_decrypt_test() throws Exception {
   String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJggkaRJ+bqLMF6pefubEDLViboxYKGTdGe+78DziIta8Nv8crOA83M0tFG8y8CqHcFYIbG89q9zcnNvL+E2/CECAwEAAQ==";
   String password = "AgDRyKJ81Ku3o0HSyalDgCTtGsWcKz3fC0iM5pLur2QJnIF+fKWKFZ6c6e36M06tF2uCadvS/EodWxmRDWwvIA==";
   System.out.println(ConfigTools.decrypt(publicKey, password));
}

实现方案

SpringBoot 集成 Druid 数据库链接加密的方式,推荐使用配置注入方式初始化 DataSource。方法如下:

一、增加配置注入 Bean

import java.sql.SQLException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
 
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
public class DbConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(DbConfig.class);
 
    private String url;
    private String driverClassName;
    private String username;
    private String password;
    private Integer initialSize;
    private Integer minIdle;
    private Integer maxActive;
    private Integer maxWait;
    private Integer timeBetweenEvictionRunsMillis;
    private Integer minEvictableIdleTimeMillis;
    private String validationQuery;
    private Boolean testWhileIdle;
    private Boolean testOnBorrow;
    private Boolean testOnReturn;
    private Boolean poolPreparedStatements;
    private Integer maxOpenPreparedStatements;
    private Integer maxPoolPreparedStatementPerConnectionSize;
    private String filters;
    private String publicKey;
    private String connectionProperties;
 
    @Primary
    @Bean(name = "dataSource")
    public DataSource dataSource() {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(url);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        datasource.setConnectionProperties(connectionProperties.replace("${publicKey}", publicKey));
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            LOGGER.error("druid configuration initialization filter", e);
        }
        return datasource;
    }
 
    public String getUrl() {
        return url;
    }
 
    public void setUrl(String url) {
        this.url = url;
    }
 
    public String getDriverClassName() {
        return driverClassName;
    }
 
    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
 
    public Integer getInitialSize() {
        return initialSize;
    }
 
    public void setInitialSize(Integer initialSize) {
        this.initialSize = initialSize;
    }
 
    public Integer getMinIdle() {
        return minIdle;
    }
 
    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }
 
    public Integer getMaxActive() {
        return maxActive;
    }
 
    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }
 
    public Integer getMaxWait() {
        return maxWait;
    }
 
    public void setMaxWait(Integer maxWait) {
        this.maxWait = maxWait;
    }
 
    public Integer getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }
 
    public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }
 
    public Integer getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }
 
    public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }
 
    public String getValidationQuery() {
        return validationQuery;
    }
 
    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }
 
    public Boolean getTestWhileIdle() {
        return testWhileIdle;
    }
 
    public void setTestWhileIdle(Boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }
 
    public Boolean getTestOnBorrow() {
        return testOnBorrow;
    }
 
    public void setTestOnBorrow(Boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }
 
    public Boolean getTestOnReturn() {
        return testOnReturn;
    }
 
    public void setTestOnReturn(Boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }
 
    public Boolean getPoolPreparedStatements() {
        return poolPreparedStatements;
    }
 
    public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }
 
    public Integer getMaxOpenPreparedStatements() {
        return maxOpenPreparedStatements;
    }
 
    public void setMaxOpenPreparedStatements(Integer maxOpenPreparedStatements) {
        this.maxOpenPreparedStatements = maxOpenPreparedStatements;
    }
 
    public Integer getMaxPoolPreparedStatementPerConnectionSize() {
        return maxPoolPreparedStatementPerConnectionSize;
    }
 
    public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
        this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
    }
 
    public String getFilters() {
        return filters;
    }
 
    public void setFilters(String filters) {
        this.filters = filters;
    }
 
    public String getPublicKey() {
        return publicKey;
    }
 
    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }
 
    public String getConnectionProperties() {
        return connectionProperties;
    }
 
    public void setConnectionProperties(String connectionProperties) {
        this.connectionProperties = connectionProperties;
    }
}

二、增加配置文件

# mysql config
spring.datasource.name=pcdsdata
spring.datasource.url=jdbc:mysql://192.168.1.1/mydb
spring.datasource.username=root
spring.datasource.password=AgDRyKJ81Ku3o0HSyalDgCTtGsWcKz3fC0iM5pLur2QJnIF+fKWKFZ6c6e36M06tF2uCadvS/EodWxmRDWwvIA==
 
# druid datasource config
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.maxActive=20
spring.datasource.initialSize=1
spring.datasource.minIdle=1
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=select 1 from dual
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxOpenPreparedStatements=20
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.filters=config,stat,wall,log4j
spring.datasource.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJggkaRJ+bqLMF6pefubEDLViboxYKGTdGe+78DziIta8Nv8crOA83M0tFG8y8CqHcFYIbG89q9zcnNvL+E2/CECAwEAAQ==
spring.datasource.connectionProperties=config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}

注意事项

1)config.decrypt=true;config.decrypt.key=  该两项配置为 Druid 内部固定 Key。网上很多案例配置为 publicKey,这是有问题的。
2)其他教程会配置 DbType 为 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource 实际在 Druid 源码中会判断 DbType 为 MySql 或者 Db2 等类型。可删除此项 原逻辑将从  this.dbType = JdbcUtils.getDbType(jdbcUrl, null);//数据库链接字符串 获取 DbType 值

经过以上配置,SpringBoot 使用 Druid 加密链接完成。如果过程中遇到其他问题可在下方留言,方便更多人解决类似问题。

经测试以上配置兼容 SpringBoot2.0 以上 2.0.3/2.06 等. 升级测试时需要注意 jar 包版本问题:
1)Gson 升级到最新版本,如 gson-2.8.5.jar. 否则报错 com.google.gson.GsonBuilder.setLenient()Lcom/google/gson/GsonBuilder; but it does not exist. Its class, com.google.gson.GsonBuilder, is available from the following locations:

2)log4j 升级到 log4j-1.2.17 以上, 否则报错 Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Priority


源码分析

1) 根据源码逻辑,如果不自定义注入 Bean, 在 SpringBoot 初始化时只会读取系统配置参数,如下

public DruidDataSource(){
        this(false);
    }
 
    public DruidDataSource(boolean fairLock){
        super(fairLock);
 
        configFromPropety(System.getProperties());
    }
 
    public void configFromPropety(Properties properties) {
        {
            Boolean value = getBoolean(properties, "druid.testWhileIdle");
            if (value != null) {
                this.setTestWhileIdle(value);
            }
        }
        {
            Boolean value = getBoolean(properties, "druid.testOnBorrow");
            if (value != null) {
                this.setTestOnBorrow(value);
            }
        }
 
 
...

2) 所有的配置在类 DruidAbstractDataSource 中,自定义注入 Bean,将在这个类中设置相关属性

3) 在首次初始化数据库链接时将调用 DruidDataSource.init(),并进入到 ConfigFilter.init ,初始化建立链接。根据配置config.decrypt 决定是否要进行解密动作,如需解密则加载config.decrypt.key 和 password(首先加载 connectionProperties 链接字符串中的 password,没有再加载默认的 spring.datasource.password)  ConfigFilter.decrypt 执行解密动作。

4) 文章《springboot 结合 Druid 加密数据库密码遇到的坑!》中其实是绕过了是否要进行加密等配置,自己实现了解密动作再把数据库链接密码替换掉,这违背了 Druid 设计的初衷了。​

参考资料

参考资料 https://github.com/alibaba/druid/wiki


百味博客 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:SpringBoot 使用 Druid 数据库加密链接完整方案
喜欢 (8)
[微信扫一扫]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(7)个小伙伴在吐槽
  1. 学无止境,认真拜读!
    daxi2018-11-03 09:10 回复
  2. 来看看,因为,总能学到东西!
    xing2018-11-12 07:22 回复
  3. 时间真快,又到年底!正好有空,到这里看看!
    域名2018-11-23 09:24 回复
  4. 你的Spring Boot是什么版本的呢?我用的是Spring Boot2.0版本,能兼容吗?
    rayyq2019-02-20 19:40 回复
    • 测试了Spring boot 2.0.6 的版本,也是可以正常运行的
      bywei2019-03-06 16:47 回复