前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot之druid数据库密码加密实战

springboot之druid数据库密码加密实战

作者头像
lyb-geek
发布2022-03-10 13:42:18
1.5K0
发布2022-03-10 13:42:18
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

前言

最近接了一个外包单(基于springboot2,连接池为druid),客户经费有限,基本上要啥,啥没有,项目基本上是托管在私人的某gay,某云等,本着让客户放心的原则,就在安全方面多考虑了一点,首先比如数据库密码加密之类的,虽然要是有心要破解也是容易,但至少加密给自己心里一点暗示。。。废话有点多,进入正题,本文主要分为3个部分,第一部分是单个数据源密码加密,第二部分是多个数据源密码加密,第三部分是简要的解密源码分析。

单数据源密码加密

1、 下载druid.jar,可以从maven中央仓库上下载

https://mvnrepository.com/artifact/com.alibaba/druid

我这边下载的版本是如下

代码语言:javascript
复制
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>

这个jar主要是用来生成加密密码

2、生成加密密码

利用刚才下好的jar,在cmd中执行如下命令

代码语言:javascript
复制
java -cp druid-1.1.10.jar com.alibaba.druid.filter.config.ConfigTools test

注:test为你数据库的密码

对我们有用的是publicKey和加密后的password,这个publickey主要是用来解密的秘钥

3、修改springboot配置文件配置,参考如下

代码语言:javascript
复制
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKXfJyPsQ1rvSQXO+8m1TrIWS5XSSwzwDBIjPGZNbpZ10+Tai7k1GMzF6eufgMNWlNwOHJvxIYwjrts8b4UbSiECAwEAAQ==
    druid:
        url: jdbc:mysql://lcoahost:3306/test1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
        username: root
        password: L/Jlcu+tiIgvq9wEvnycxvEE3+RVixnY/YgUB/5mAdO1WLdlrt2CipYxGjnS/4A+NtR0TTldmItzY4UtbSRe6g==
      initial-size: 10
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      #validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        #login-username: admin
        #login-password: admin
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: false
        wall:
          config:
            multi-statement-allow: true
        config:
          enabled: true 
      connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}

3.1、配置文件跟加密相关的属性

spring.datasource.druid.connection-properties

这个属性配置的value是键值对,其中config.decrypt=true表示要进行解密,config.decrypt.key=${spring.datasource.publicKey}注入要解密需要的公钥

spring.datasource.druid.filter.config.enabled=true

开启configFilter,这个不开启是没办法进行解密操作的

多数据源密码加密配置

1、对数据库密码进行加密

这个步骤和单数据源密码加密一样,就略过

2、修改springboot配置文件参考如下

代码语言:javascript
复制
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKXfJyPsQ1rvSQXO+8m1TrIWS5XSSwzwDBIjPGZNbpZ10+Tai7k1GMzF6eufgMNWlNwOHJvxIYwjrts8b4UbSiECAwEAAQ==
    druid:
      first:  #数据源1
        url: jdbc:mysql://localhost:3306/test1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
        username: root
        password: L/Jlcu+tiIgvq9wEvnycxvEE3+RVixnY/YgUB/5mAdO1WLdlrt2CipYxGjnS/4A+NtR0TTldmItzY4UtbSRe6g==
      second:  #数据源2
        url: jdbc:mysql://localhost2:3306/test2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
        username: root
        password: L/Jlcu+tiIgvq9wEvnycxvEE3+RVixnY/YgUB/5mAdO1WLdlrt2CipYxGjnS/4A+NtR0TTldmItzY4UtbSRe6g==
      initial-size: 10
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      #validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        #login-username: admin
        #login-password: admin
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: false
        wall:
          config:
            multi-statement-allow: true

从配置文件上看,有没有发现单数据源说要配置属性,多数据源竟然不用配置

代码语言:javascript
复制
spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}


spring.datasource.druid.filter.config.enabled=true

没配置的原因是,多数据源注入会在过滤器解密之前,这会导致数据源注入加密的密码,而由于没有解密,导致连不到数据库,因此配置了也没用,其次如果多个数据源的数据库密码不一样,产生的公钥都是不一样的,用原生提供的configFilter没办法进行解析,为啥这么说,后面源码解析会说。如果配置不行,那可以从代码层面上考虑

3、代码层进行多数据源密码解密,其代码如下

代码语言:javascript
复制
@Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST,  this.getDataSourceWithDecryptPwd(firstDataSource));
        targetDataSources.put(DataSourceNames.SECOND, this.getDataSourceWithDecryptPwd(secondDataSource));
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }

    private DataSource getDataSourceWithDecryptPwd(DataSource dataSource){
        try {
            if(dataSource instanceof  DruidDataSource){
                DruidDataSource druidDataSource = (DruidDataSource) dataSource;
                String passwordPlainText = ConfigTools.decrypt(publicKey, druidDataSource.getPassword());
                druidDataSource.setPassword(passwordPlainText);
                return druidDataSource;
            }
        } catch (Exception e) {
            logger.error("getDataSourceWithDecryptPwd error:"+e.getMessage(),e);
        }

        return dataSource;

    }

其核心原理就是在多数据源注入之前,进行密码解密,解密的核心方法是由阿里提供工具类

代码语言:javascript
复制
com.alibaba.druid.filter.config.ConfigTools

druid数据库密码解密源码分析

之前我们单数据源提到为什么要开启configfilter,不然解密无法操作,我们看下这个类到底是做了啥

代码语言:javascript
复制
public class ConfigFilter extends FilterAdapter {
    private static Log LOG = LogFactory.getLog(ConfigFilter.class);
    public static final String CONFIG_FILE = "config.file";
    public static final String CONFIG_DECRYPT = "config.decrypt";
    public static final String CONFIG_KEY = "config.decrypt.key";
    public static final String SYS_PROP_CONFIG_FILE = "druid.config.file";
    public static final String SYS_PROP_CONFIG_DECRYPT = "druid.config.decrypt";
    public static final String SYS_PROP_CONFIG_KEY = "druid.config.decrypt.key";

    public ConfigFilter() {
    }

    public void init(DataSourceProxy dataSourceProxy) {
        if (!(dataSourceProxy instanceof DruidDataSource)) {
            LOG.error("ConfigLoader only support DruidDataSource");
        }

        DruidDataSource dataSource = (DruidDataSource)dataSourceProxy;
        Properties connectionProperties = dataSource.getConnectProperties();
        Properties configFileProperties = this.loadPropertyFromConfigFile(connectionProperties);
        boolean decrypt = this.isDecrypt(connectionProperties, configFileProperties);
        if (configFileProperties == null) {
            if (decrypt) {
                this.decrypt(dataSource, (Properties)null);
            }

        } else {
            if (decrypt) {
                this.decrypt(dataSource, configFileProperties);
            }

            try {
                DruidDataSourceFactory.config(dataSource, configFileProperties);
            } catch (SQLException var7) {
                throw new IllegalArgumentException("Config DataSource error.", var7);
            }
        }
    }

    public boolean isDecrypt(Properties connectionProperties, Properties configFileProperties) {
        String decrypterId = connectionProperties.getProperty("config.decrypt");
        if ((decrypterId == null || decrypterId.length() == 0) && configFileProperties != null) {
            decrypterId = configFileProperties.getProperty("config.decrypt");
        }

        if (decrypterId == null || decrypterId.length() == 0) {
            decrypterId = System.getProperty("druid.config.decrypt");
        }

        return Boolean.valueOf(decrypterId);
    }

    Properties loadPropertyFromConfigFile(Properties connectionProperties) {
        String configFile = connectionProperties.getProperty("config.file");
        if (configFile == null) {
            configFile = System.getProperty("druid.config.file");
        }

        if (configFile != null && configFile.length() > 0) {
            if (LOG.isInfoEnabled()) {
                LOG.info("DruidDataSource Config File load from : " + configFile);
            }

            Properties info = this.loadConfig(configFile);
            if (info == null) {
                throw new IllegalArgumentException("Cannot load remote config file from the [config.file=" + configFile + "].");
            } else {
                return info;
            }
        } else {
            return null;
        }
    }

    public void decrypt(DruidDataSource dataSource, Properties info) {
        try {
            String encryptedPassword = null;
            if (info != null) {
                encryptedPassword = info.getProperty("password");
            }

            if (encryptedPassword == null || encryptedPassword.length() == 0) {
                encryptedPassword = dataSource.getConnectProperties().getProperty("password");
            }

            if (encryptedPassword == null || encryptedPassword.length() == 0) {
                encryptedPassword = dataSource.getPassword();
            }

            PublicKey publicKey = this.getPublicKey(dataSource.getConnectProperties(), info);
            String passwordPlainText = ConfigTools.decrypt(publicKey, encryptedPassword);
            if (info != null) {
                info.setProperty("password", passwordPlainText);
            } else {
                dataSource.setPassword(passwordPlainText);
            }

        } catch (Exception var6) {
            throw new IllegalArgumentException("Failed to decrypt.", var6);
        }
    }

    public PublicKey getPublicKey(Properties connectionProperties, Properties configFileProperties) {
        String key = null;
        if (configFileProperties != null) {
            key = configFileProperties.getProperty("config.decrypt.key");
        }

        if (StringUtils.isEmpty(key) && connectionProperties != null) {
            key = connectionProperties.getProperty("config.decrypt.key");
        }

        if (StringUtils.isEmpty(key)) {
            key = System.getProperty("druid.config.decrypt.key");
        }

        return ConfigTools.getPublicKey(key);
    }

    public Properties loadConfig(String filePath) {
        Properties properties = new Properties();
        InputStream inStream = null;

        URL url;
        try {
            boolean xml = false;
            if (filePath.startsWith("file://")) {
                filePath = filePath.substring("file://".length());
                inStream = this.getFileAsStream(filePath);
                xml = filePath.endsWith(".xml");
            } else if (!filePath.startsWith("http://") && !filePath.startsWith("https://")) {
                if (filePath.startsWith("classpath:")) {
                    String resourcePath = filePath.substring("classpath:".length());
                    inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath);
                    xml = resourcePath.endsWith(".xml");
                } else {
                    inStream = this.getFileAsStream(filePath);
                    xml = filePath.endsWith(".xml");
                }
            } else {
                url = new URL(filePath);
                inStream = url.openStream();
                xml = url.getPath().endsWith(".xml");
            }

            if (inStream == null) {
                LOG.error("load config file error, file : " + filePath);
                url = null;
                return url;
            }

            if (xml) {
                properties.loadFromXML(inStream);
            } else {
                properties.load(inStream);
            }

            Properties var12 = properties;
            return var12;
        } catch (Exception var9) {
            LOG.error("load config file error, file : " + filePath, var9);
            url = null;
        } finally {
            JdbcUtils.close(inStream);
        }

        return url;
    }

    private InputStream getFileAsStream(String filePath) throws FileNotFoundException {
        InputStream inStream = null;
        File file = new File(filePath);
        if (file.exists()) {
            inStream = new FileInputStream(file);
        } else {
            inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);
        }

        return (InputStream)inStream;
    }
}

从源码上我们可以知道这个类主要做的事情,加载配置文件信息,解密,把解密后的密码重新设置进druid数据源中,其核心方法是decrypt,这个方法主要用来解密,这方法里面里面重点关注

代码语言:javascript
复制
PublicKey publicKey = this.getPublicKey(dataSource.getConnectProperties(), info);
            String passwordPlainText = ConfigTools.decrypt(publicKey, encryptedPassword);
            if (info != null) {
                info.setProperty("password", passwordPlainText);
            } else {
                dataSource.setPassword(passwordPlainText);
            }

看到没有,里面的方法片段有个解密关键

代码语言:javascript
复制
String passwordPlainText = ConfigTools.decrypt(publicKey, encryptedPassword);

现在来解答用原生提供的configFilter没办法进行解析,我们看下如下方法

代码语言:javascript
复制
PublicKey publicKey = this.getPublicKey(dataSource.getConnectProperties(), info);
代码语言:javascript
复制
public PublicKey getPublicKey(Properties connectionProperties, Properties configFileProperties) {
        String key = null;
        if (configFileProperties != null) {
            key = configFileProperties.getProperty("config.decrypt.key");
        }

        if (StringUtils.isEmpty(key) && connectionProperties != null) {
            key = connectionProperties.getProperty("config.decrypt.key");
        }

        if (StringUtils.isEmpty(key)) {
            key = System.getProperty("druid.config.decrypt.key");
        }

        return ConfigTools.getPublicKey(key);
    }

很显然传入多个publickey,会被它当成一个,因此要实现识别多个publickey就要额外自定过滤器进行扩展了

总结

druid数据库密码加密原理上不会很难,其实不少开发正常都不会对数据库密码再进行加密,可能是出于性能考虑,没必要去实现这样一个看似鸡肋的功能,可能觉得因为平时项目都是部署在内网里面,就算密码被知道了,也没啥,写这篇文章主要因为很少有看到百度上有对多数据源druid数据库密码加密的讲解,可能是因为这个太简单了原因吧,哈哈哈哈哈哈

本文参与?腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-02-03,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 单数据源密码加密
    • 1、 下载druid.jar,可以从maven中央仓库上下载
      • 2、生成加密密码
      • 3、修改springboot配置文件配置,参考如下
        • 3.1、配置文件跟加密相关的属性
        • 多数据源密码加密配置
          • 1、对数据库密码进行加密
            • 2、修改springboot配置文件参考如下
              • 3、代码层进行多数据源密码解密,其代码如下
              • druid数据库密码解密源码分析
              • 总结
              相关产品与服务
              数据库
              云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
              http://www.vxiaotou.com