1. 程式人生 > >SpringBoot使用Druid數據庫加密鏈接完整方案

SpringBoot使用Druid數據庫加密鏈接完整方案

生成 too ntp conn github cut 解密 hub 資料

網上的坑

springboot 使用 Druid 數據庫加密鏈接方案,不建議采用網上的一篇文章《springboot 結合 Druid 加密數據庫密碼遇到的坑!》介紹的方式來進行加密鏈接實現。本文章下文分享 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;
    }
 
    
publicvoid setUsername(String username){ this.username = username; } publicString getPassword(){ return password; } publicvoid setPassword(String password){ this.password = password; } publicInteger getInitialSize(){ return initialSize; } publicvoid setInitialSize(Integer initialSize){ this.initialSize = initialSize; } publicInteger getMinIdle(){ return minIdle; } publicvoid setMinIdle(Integer minIdle){ this.minIdle = minIdle; } publicInteger getMaxActive(){ return maxActive; } publicvoid setMaxActive(Integer maxActive){ this.maxActive = maxActive; } publicInteger getMaxWait(){ return maxWait; } publicvoid setMaxWait(Integer maxWait){ this.maxWait = maxWait; } publicInteger getTimeBetweenEvictionRunsMillis(){ return timeBetweenEvictionRunsMillis; } publicvoid setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis){ this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; } publicInteger getMinEvictableIdleTimeMillis(){ return minEvictableIdleTimeMillis; } publicvoid setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis){ this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; } publicString getValidationQuery(){ return validationQuery; } publicvoid setValidationQuery(String validationQuery){ this.validationQuery = validationQuery; } publicBoolean getTestWhileIdle(){ return testWhileIdle; } publicvoid setTestWhileIdle(Boolean testWhileIdle){ this.testWhileIdle = testWhileIdle; } publicBoolean getTestOnBorrow(){ return testOnBorrow; } publicvoid setTestOnBorrow(Boolean testOnBorrow){ this.testOnBorrow = testOnBorrow; } publicBoolean getTestOnReturn(){ return testOnReturn; } publicvoid setTestOnReturn(Boolean testOnReturn){ this.testOnReturn = testOnReturn; } publicBoolean getPoolPreparedStatements(){ return poolPreparedStatements; } publicvoid setPoolPreparedStatements(Boolean poolPreparedStatements){ this.poolPreparedStatements = poolPreparedStatements; } publicInteger getMaxOpenPreparedStatements(){ return maxOpenPreparedStatements; } publicvoid setMaxOpenPreparedStatements(Integer maxOpenPreparedStatements){ this.maxOpenPreparedStatements = maxOpenPreparedStatements; } publicInteger getMaxPoolPreparedStatementPerConnectionSize(){ return maxPoolPreparedStatementPerConnectionSize; } publicvoid setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize){ this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize; } publicString getFilters(){ return filters; } publicvoid setFilters(String filters){ this.filters = filters; } publicString getPublicKey(){ return publicKey; } publicvoid setPublicKey(String publicKey){ this.publicKey = publicKey; } publicString getConnectionProperties(){ return connectionProperties; } publicvoid 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 加密鏈接完成。如果過程中遇到其他問題可在下方留言,方便更多人解決類似問題。


源碼分析

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

SpringBoot使用Druid數據庫加密鏈接完整方案