SpringBoot配置mysql加密之Druid方式

一、导入Druid依赖

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.22</version>
</dependency>

二、生成密文

方式1. 找到存放druid jar包的目录

1-1、在目录栏执行cmd,调出命令行窗口

在这里插入图片描述

1-2、在命令行窗口执行
java -cp druid-1.1.22.jar com.alibaba.druid.filter.config.ConfigTools 数据库密码## 如下
E:\java\.m2\repository\com\alibaba\druid\1.1.22> java -cp .\druid-1.1.22.jar com.alibaba.druid.filter.config.ConfigTools ltkj.test
privateKey:MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAsQxrpzOi7BscR8nc5YIxRS8XUk3vN92V1wMUqu5EOKKr9D9MGVCZ3+7REzTB2uHUbRZgd4oCu7XwuLAx3GdOvwIDAQABAkAVDS2FuYGPQl/39zwhO/xAxisnOaYOhfXplW+xLViGRTqC4dTQ2h2LVMGpYhOPXCMNeMAO6ZhH4mSqNNMs16ClAiEAwaV1H8gjwFAfpMNhX76YjhtNkyt9dc8A2NbCqYvi6rMCIQDqDshX465wOSbnpXacW/8Uwi2Ku727YNFDKXJ9I6oBxQIgbjf0wFA0OSPhvvAOHmbnggr8ToX0dPeLreAfEE20rI0CIQCLSZrOOu9/V3OgnSZV7KWDW/8wNYO2s+o0tsCsWgH9JQIgPzjgmBX8s5CZwzxd2JIxAjH761CdNhTAmZjPPYuNLJg=
publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALEMa6czouwbHEfJ3OWCMUUvF1JN7zfdldcDFKruRDiiq/Q/TBlQmd/u0RM0wdrh1G0WYHeKAru18LiwMdxnTr8CAwEAAQ==
password:omjlkBmUL7vXvuiOUEsYBNejb9j1GuD7aCQWxWLSR9LohB5H7M+X8LR63wBUeyws7yRPNq/zejMAsZ82DX3z1A==PS E:\java\.m2\repository\com\alibaba\druid\1.1.22> 

复制password和publickey

方式2. 工具类生成
利用工具类生成加密后密码和公钥
public static void main(String[] args) throws Exception {String password = "root123";System.out.println("明文密码: " + password);String[] keyPair = ConfigTools.genKeyPair(512);//私钥String privateKey = keyPair[0];//公钥String publicKey = keyPair[1];//用私钥加密后的密文password = ConfigTools.encrypt(privateKey, password);System.out.println("privateKey:" + privateKey);System.out.println("publicKey:" + publicKey);System.out.println("password:" + password);String decryptPassword = ConfigTools.decrypt(publicKey, password);System.out.println("解密后:" + decryptPassword);}

三、项目配置

修改yml配置文件

1. filters添加config
2. 配置解密,同时指定公钥

2-1. 生产环境 建议如下配置
启动时,通过java -jar 启动命令,指定spring.druid.publickey的值,如(java -jar xx.jar --spring.druid.publickey=公钥),
避免通过yml获取到公钥。
2-2 . 开发环境可将公钥直接配置在idea启动参数内。

connectionProperties: config.decrypt=true;config.decrypt.key=${spring.druid.publickey}
datasource:username: rootpassword: RuacV1QzH80HVwZpR5MqagLoOWbRPYPj+yXKJrfEXJxIVchnWfGpdi2PTJCAlWoi7hNN+y4hhDmiGEvdYscW4w==url: jdbc:mysql://localhost:3306/article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource#druid 数据源专有配置druid:#配置druid监控页面stat-view-servlet:enabled: trueurl-pattern: /druid/*loginUsername: adminloginPassword: 123456initialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: false# 打开PSCachepoolPreparedStatements: true#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入#如果运行时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority#则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4jDruidDataSourceFactoryfilters: stat,wall,config#指定每个连接上PSCache的大小maxPoolPreparedStatementPerConnectionSize: 20#合并多个DruidDataSource的监控数据useGlobalDataSourceStat: true#通过connectProperties属性来打开mergeSql功能;慢SQL记录;配置数据库密码解密;connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000;config.decrypt=true;config.decrypt.key=${spring.druid.publickey}

Note: 需要注意的是

druid是用私钥加密,公钥解密的,即如果公钥为所有人可见,那么所有人均可以解密。因此,上述配置若在团队内可见,没什么影响,但是如果配置文件万一被放到了公网上,相当于把钥匙插在锁上,白加密了。。在生产环境下,建议改为如下配置:

--- # 临时文件存储位置 避免临时文件被系统清理报错
spring.servlet.multipart.location: /ltkj/server/temp--- # 监控中心配置
spring:boot:admin:client:# 增加客户端开关enabled: trueurl: http://localhost:9094/adminusername: ltkjpassword: 123456# 将client的用户名密码发送至serverinstance:service-host-type: IPmetadata:user.name: ltkjuser.password: 123456servlet:multipart:max-file-size: 5000MBmax-request-size: 5000MB--- # xxl-job 配置
xxl.job:# 执行器开关enabled: false# 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。admin-addresses: http://localhost:9200/xxl-job-admin# 执行器通讯TOKEN:非空时启用access-token: xxl-jobexecutor:# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册appname: xxl-job-executor# 执行器端口号 执行器从9101开始往后写port: 9101# 执行器注册:默认IP:PORTaddress:# 执行器IP:默认自动获取IPip: 127.0.0.1# 执行器运行日志文件存储磁盘路径logpath: ./logs/xxl-job# 执行器日志文件保存天数:大于3生效logretentiondays: 30--- # 数据源配置
spring:datasource:#    type: com.zaxxer.hikari.HikariDataSourcetype: com.alibaba.druid.pool.DruidDataSource# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/contentdynamic:# 性能分析插件(有性能损耗 不建议生产环境使用)p6spy: false# 设置默认的数据源或者数据源组,默认值即为 masterprimary: master# 严格模式 匹配不到数据源则报错strict: truedatasource:# 主库数据源master:type: ${spring.datasource.type}driverClassName: com.mysql.cj.jdbc.Driver# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)url: jdbc:mysql://192.168.234.128:3306/ltkj-personnel-manager?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=trueusername: ltkjtestpassword: omjlkBmUL7vXvuiOUEsYBNejb9j1GuD7aCQWxWLSR9LohB5H7M+X8LR63wBUeyws7yRPNq/zejMAsZ82DX3z1A==druid:filters: configconnection-properties: config.decrypt=truepublic-key: ${ltkj.publickey}#          url: jdbc:mysql://192.168.75.131:3306/ltkj-personnel-manager?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true#          username: root#          password: root# 从库数据源slave:lazy: truetype: ${spring.datasource.type}driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.234.128:3306/ltkj-personnel-manager?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=trueusername: ltkjtestpassword: omjlkBmUL7vXvuiOUEsYBNejb9j1GuD7aCQWxWLSR9LohB5H7M+X8LR63wBUeyws7yRPNq/zejMAsZ82DX3z1A==druid:filters: configconnection-properties: config.decrypt=truepublic-key: ${spring.datasource.druid.public-key}#          url: jdbc:mysql://192.168.75.131:3306/ltkj-personnel-manager?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
#          username: root
#          password: root--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring:redis:# 地址
#    host: 192.168.0.225host: localhost# 端口,默认为6379port: 6379# 数据库索引database: 2# 密码(如没有密码请注释掉)password: ltkj.test# 连接超时时间timeout: 10s# 是否开启sslssl: falseredisson:# redis key前缀keyPrefix:# 线程池数量threads: 16# Netty线程池数量nettyThreads: 32# 单节点配置singleServerConfig:# 客户端名称clientName: ${ltkj.name}# 最小空闲连接数connectionMinimumIdleSize: 32# 连接池大小connectionPoolSize: 64# 连接空闲超时,单位:毫秒idleConnectionTimeout: 10000# 命令等待超时,单位:毫秒timeout: 3000# 发布和订阅连接池大小subscriptionConnectionPoolSize: 50--- # mail 邮件发送
mail:enabled: falsehost: smtp.163.comport: 465# 是否需要用户名密码验证auth: true# 发送方,遵循RFC-822标准from: xxx@163.com# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)user: xxx@163.com# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)pass: xxxxxxxxxx# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。starttlsEnable: true# 使用SSL安全连接sslEnable: true# SMTP超时时长,单位毫秒,缺省值不超时timeout: 0# Socket连接超时值,单位毫秒,缺省值不超时connectionTimeout: 10--- # sms 短信
sms:enabled: false# 阿里云 dysmsapi.aliyuncs.com# 腾讯云 sms.tencentcloudapi.comendpoint: "dysmsapi.aliyuncs.com"accessKeyId: xxxxxxxaccessKeySecret: xxxxxxsignName: 测试# 腾讯专用sdkAppId:#file
file:path: /data/wwwroot/static.ltkj.com/personnel
#  host: https://static.ltkj.comhost: http://192.168.0.225:9002

一行之差:

config.decrypt.key: ${spring.datasource.druid.publickey},

这里${spring.datasource.druid.publickey}可以有两种传入方式:

通过服务启动参数

java -jar xxx.jar --spring.datasource.druid.publickey=
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIYYdcdptMU6n/4wtb7StmX4LFvmlw7+b5KHm7L8C0txn1+iMeXz3FM7emkGkKMuaLd9OazkjgxNpPCDRaNM7ecCAwEAAQ==

通过系统环境变量
这个可以通过Idea IDE传入(开发环境),或者实际的系统环境变量传入(生产环境)。

在这里插入图片描述

总结

实现对配置文件敏感数据的加密,方式很多,但一定要注意安全性,不可以把公钥公开配置在文件中;还是开头那句话:

一把插着钥匙的锁,能说它是安全的吗?

测试环境的问题:

convert java.lang.String to java.lang.Class<javax.sql.DataSource

如果 遇到上面的问题,因为 druid-spring-boot-starter 包是新加的, 没有上传到测试环境的 lib/ 下,

  • 清空 lib/ ,再rz全部 上传依赖包

在这里插入图片描述

在这里插入图片描述

四、ConfigFilter解密原理分析

对于上面为何通过dataSource.setFilters(“config”)一行代码就能实现数据库密码的解密功能,你心中是否有疑惑,它具体又是如何配置了一个ConfigFilter实例的呢?带着这个疑问,我们看下DruidDataSource类中两个重要的方法入手:setFilters和setConnectionproperties,通过这两个入口方法找到与数据库连接密码解密有关的源码实现

3.1 DruidDataSource#setFilters方法

public void setFilters(String filters) throws SQLException {if (filters != null && filters.startsWith("!")) {filters = filters.substring(1);this.clearFilters();}this.addFilters(filters);}public void addFilters(String filters) throws SQLException {if (filters == null || filters.length() == 0) {return;}// 多个filter通过逗号分隔String[] filterArray = filters.split("\\,");for (String item : filterArray) {FilterManager.loadFilter(this.filters, item.trim());}}

在上面的addFilters方法中会去遍历配置的filter数组并调用FilterManager#loadFilter方法加载过滤器

3.2 FilterManager类静态代码块
而在FilterManager类中有这样一段静态代码

static {try {Properties filterProperties = loadFilterConfig();for (Map.Entry<Object, Object> entry : filterProperties.entrySet()) {String key = (String) entry.getKey();if (key.startsWith("druid.filters.")) {String name = key.substring("druid.filters.".length());aliasMap.put(name, (String) entry.getValue());}}} catch (Throwable e) {LOG.error("load filter config error", e);}}

上面这段静态代码首先会去调用无参的loadFilterConfig方法加载过滤器配置

public static Properties loadFilterConfig() throws IOException {Properties filterProperties = new Properties();loadFilterConfig(filterProperties, ClassLoader.getSystemClassLoader());loadFilterConfig(filterProperties, FilterManager.class.getClassLoader());loadFilterConfig(filterProperties, Thread.currentThread().getContextClassLoader());return filterProperties;}

而上面的静态方法中又会去调用带两个参数的loadFilterConfig方法,加载druid.jar包中类路径下的META-INF/druid-filter.properties属性配置文件

我们来看下druid-filter.properties文件中有哪些过滤器

druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
druid.filters.haRandomValidator=com.alibaba.druid.pool.ha.selector.RandomDataSourceValidateFilter

可以看到总共有13个过滤器,ConfigFilter类对应的key为druid.filters.config
然后我们回到最上面的静态代码块中可以看到程序会遍历加载并读取druid-filter.properties文件中属性变量后返回的filterProperties, 并将其中的key截取掉druid.filters.前缀后的字符串作为name和过滤器的全类名作为键值对保存在ConcurrentHashMap<String, String>数据结构的aliasMap属性中。

3.3 FilterManager#loadFilter方法
————————————————

public static void loadFilter(List<Filter> filters, String filterName) throws SQLException {if (filterName.length() == 0) {return;}String filterClassNames = getFilter(filterName);if (filterClassNames != null) {for (String filterClassName : filterClassNames.split(",")) {if (existsFilter(filters, filterClassName)) {continue;}Class<?> filterClass = Utils.loadClass(filterClassName);if (filterClass == null) {LOG.error("load filter error, filter not found : " + filterClassName);continue;}Filter filter;try {filter = (Filter) filterClass.newInstance();} catch (ClassCastException e) {LOG.error("load filter error.", e);continue;} catch (InstantiationException e) {thrownew SQLException("load managed jdbc driver event listener error. " + filterName, e);} catch (IllegalAccessException e) {thrownew SQLException("load managed jdbc driver event listener error. " + filterName, e);} catch (RuntimeException e) {thrownew SQLException("load managed jdbc driver event listener error. " + filterName, e);}filters.add(filter);}return;}if (existsFilter(filters, filterName)) {return;}Class<?> filterClass = Utils.loadClass(filterName);if (filterClass == null) {LOG.error("load filter error, filter not found : " + filterName);return;}try {Filter filter = (Filter) filterClass.newInstance();filters.add(filter);} catch (Exception e) {thrownew SQLException("load managed jdbc driver event listener error. " + filterName, e);}}
————————————————

上面这个方法的目的就是去根据配置的filterName去aliasMap中找到全类名,然后使用类加载器根据filter的全类名加载Filter类并实例化,完成实例化后将Filter类实例添加到DruidDataSource类List数据结构的filters属性中;当然这个过程首先会去判断filters中是否已经有了配置的Filter类实例,有的化则无需再次加载和实例化。

3.4 数据库连接密文解密的具体实现
在ConfigFilter类中有个init方法,正是在这个初始化方法中完成了数据源加密密码的解密

public void init(DataSourceProxy dataSourceProxy) {// 传入的dataSourceProxy就是我们的DruidDatasource实例  if (!(dataSourceProxy instanceof DruidDataSource)) {LOG.error("ConfigLoader only support DruidDataSource");}// DruidDataSource 转 DruidDataSourceDruidDataSource dataSource = (DruidDataSource) dataSourceProxy;// 获取数据源中的连接属性Properties connectionProperties = dataSource.getConnectProperties();// 加载连接属性中配置的加密属性文件Properties configFileProperties = loadPropertyFromConfigFile(connectionProperties);// 判断是否需要解密,如果需要就进行解密boolean decrypt = isDecrypt(connectionProperties, configFileProperties);if (configFileProperties == null) {if (decrypt) {decrypt(dataSource, null);}return;}if (decrypt) {decrypt(dataSource, configFileProperties);}try {DruidDataSourceFactory.config(dataSource, configFileProperties);} catch (SQLException e) {thrownew IllegalArgumentException("Config DataSource error.", e);}}
1

上面这个ConfigFilter#init方法是在DruidDatasource#init方法中触发的

for (Filter filter : filters) {filter.init(this);
}

loadPropertyFromConfigFile方法源码

publics tatic final String CONFIG_FILE = "config.file";
public static final String SYS_PROP_CONFIG_FILE = "druid.config.file";Properties loadPropertyFromConfigFile(Properties connectionProperties) {String configFile = connectionProperties.getProperty(CONFIG_FILE);if (configFile == null) {configFile = System.getProperty(SYS_PROP_CONFIG_FILE);}if (configFile != null && configFile.length() > 0) {if (LOG.isInfoEnabled()) {LOG.info("DruidDataSource Config File load from : " + configFile);}Properties info = loadConfig(configFile);if (info == null) {thrownew IllegalArgumentException("Cannot load remote config file from the [config.file=" + configFile+ "].");}return info;}return null;}

阅读loadPropertyFromConfigFile方法中的源码可见,加密属性文件主要从连接属性中key为config.file的属性文件位置或系统属性中key为druid.config.file映射的加密属性文件位置加载

isDecrypt方法源码

public static final String CONFIG_DECRYPT = "config.decrypt";
public static final String SYS_PROP_CONFIG_DECRYPT = "druid.config.decrypt";
public boolean isDecrypt(Properties connectionProperties, Properties configFileProperties) {String decrypterId = connectionProperties.getProperty(CONFIG_DECRYPT);if (decrypterId == null || decrypterId.length() == 0) {if (configFileProperties != null) {decrypterId = configFileProperties.getProperty(CONFIG_DECRYPT);}}if (decrypterId == null || decrypterId.length() == 0) {decrypterId = System.getProperty(SYS_PROP_CONFIG_DECRYPT);}return Boolean.valueOf(decrypterId);}

由isDecrypt方法中源码分析可见判断是否需要解密主要看连接属性或者加载的加密属性文件变量中key为config.decrypt的值是否为true;如果以上两个的值都不存在,则继续判断系统属性key为druid.config.decrypt的值是否为true

decrypt方法源码分析

public void decrypt(DruidDataSource dataSource, Properties info) {try {String encryptedPassword = null;// 若连接属性不为空,则从连接属性中获取加密密码,否则从数据源实例中获取加密密码if (info != null) {encryptedPassword = info.getProperty(DruidDataSourceFactory.PROP_PASSWORD);}if (encryptedPassword == null || encryptedPassword.length() == 0) {encryptedPassword = dataSource.getConnectProperties().getProperty(DruidDataSourceFactory.PROP_PASSWORD);}if (encryptedPassword == null || encryptedPassword.length() == 0) {encryptedPassword = dataSource.getPassword();}// 获取公钥PublicKey publicKey = getPublicKey(dataSource.getConnectProperties(), info);// 调用ConfigTools#decrypt方法获得解密后的密文String passwordPlainText = ConfigTools.decrypt(publicKey, encryptedPassword);if (info != null) {info.setProperty(DruidDataSourceFactory.PROP_PASSWORD, passwordPlainText);} else {dataSource.setPassword(passwordPlainText);}} catch (Exception e) {thrownew IllegalArgumentException("Failed to decrypt.", e);}}

getPublicKey方法源码

publicstaticfinal String CONFIG_KEY;static {CONFIG_KEY = "config.decrypt.key";
}
public static final String SYS_PROP_CONFIG_KEY = "druid.config.decrypt.key";
// 获取公钥
public PublicKey getPublicKey(Properties connectionProperties, Properties configFileProperties) {String key = null;if (configFileProperties != null) {key = configFileProperties.getProperty(CONFIG_KEY);}if (StringUtils.isEmpty(key) && connectionProperties != null) {key = connectionProperties.getProperty(CONFIG_KEY);}if (StringUtils.isEmpty(key)) {key = System.getProperty(SYS_PROP_CONFIG_KEY);}return ConfigTools.getPublicKey(key);}

首先会去从解析加密配制文件后的属性变量中获取公钥, 获取公钥的key为config.decrypt.key;若加密配制文件属性中不存在公钥,则去数据源的连接属性中获取key为config.decrypt.key对应的公钥,如果仍然没有则去系统属性变量中获取key为druid.config.decrypt.key对应的公钥。最后调用ConfigTools#getPublicKey方法根据传入的公钥返回一个PublicKey对象

3.5 DruidAbstractDataSource#setConnectionProperties方法源码

public void setConnectionProperties(String connectionProperties) {if (connectionProperties == null || connectionProperties.trim().length() == 0) {setConnectProperties(null);return;}// 多个连接属性使用分号分隔String[] entries = connectionProperties.split(";");Properties properties = new Properties();for (int i = 0; i < entries.length; i++) {String entry = entries[i];if (entry.length() > 0) {// 每个连接属性以=号分割成name和value两部分保存到properties属性中int index = entry.indexOf('=');if (index > 0) {String name = entry.substring(0, index);String value = entry.substring(index + 1);properties.setProperty(name, value);} else {// no value is empty string which is how java.util.Properties worksproperties.setProperty(entry, "");}}}// 最后通过抽象方法调用实现类DruidDatasource类的setConnectProperties方法setConnectProperties(properties);}

其他的源码这里就不继续深入分析了,druid.jar包中涉及到ConfigTools,DruidDatasource和ConfigFilter三个类的源码掌握到这里对于实现数据库连接密码的加密和解密也已经足够了
————————————————

五 SpringBoot 2.X 同时整合 druid连接池加密 + dynamic-datasource 动态数据源

官方文档: dynamic-datasource-spring-boot-starter

1. 引入依赖

		 <dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.22</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.2</version></dependency>
————————————————

2. 编写配置文件

# 数据源配置
spring:autoconfigure:# 排除 Druid 自动配置exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfiguredatasource:# 指定使用 Druid 数据源type: com.alibaba.druid.pool.DruidDataSource# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/contentdynamic:#设置默认的数据源或者数据源组,默认值即为 masterprimary: masterdatasource:# 主库数据源master:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.121:3306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=trueusername: rootpassword: root# 从库数据源slave:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.122:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=trueusername: rootpassword: rootdruid:# 初始连接数initialSize: 5# 最小连接池数量minIdle: 10# 最大连接池数量maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 300000# 配置一个连接在池中最大生存的时间,单位是毫秒maxEvictableIdleTimeMillis: 900000# 配置检测连接是否有效validationQuery: SELECT 1 FROM DUAL
————————————————

在这里插入图片描述

3、测试

进入 Druid 控制台
在这里插入图片描述

---------------------------------------------------------------------------------------------------------------------------------------------------------------

拓展: SpringBoot 对所有密码配置加密:Jasypt

背景

对于配置中的密码(DB, MQ, Redis等),甚至账号,在生产环境下存明文,不安全,不专业,不合适。

一把插着钥匙的锁,能说它是安全的吗?

操作流程
关于Jasypt实现对配置项的加密,网络上已经有很多这方面的资料,这里简要描述下步骤。

引入依赖

<dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>1.18</version>
</dependency>

生成密文
如果计算机上有项目用过Jasypt的,那么在maven的仓库目录下会有Jasypt的jar包。如果本地仓库没有,先下载jar包。在jar包所在目录下打开cmd命令行,键入

java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="root" password=you-guess algorithm=PBEWithMD5AndDES

最下面输出的

qN66aPx0SrcFulrPfmMXOw==

是密文,在接下来要放入配置文件。
修改已有的配置
在已有的明文配置文件中,修改Jasypt密码相关配置。

spring:datasource:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=falseusername: rootpassword: ENC(qN66aPx0SrcFulrPfmMXOw==)# 配置日志
logging:level:root: infocom.heartsuit.dao: tracepattern:console: '%p%m%n'# 加密密钥
jasypt:encryptor:password: you-guess

上面的修改主要有:
在这里插入图片描述

Note: 生成的密文要用ENC()包起来,这是Jasypt的要求。

测试修改后的配置 按照上述配置,应一切正常~~

Note: 需要注意的是:

用于生成加密后的密码的密钥不可放在配置文件或者代码中,加密密钥jasypt.encryptor.password=you-guess可以对密文解密。因此,上述配置若在团队内可见,没什么影响,但是如果配置文件万一被放到了公网上,相当于把钥匙插在锁上,白加密了。。在生产环境下,建议去掉加密密钥配置:

spring:datasource:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=falseusername: rootpassword: ENC(qN66aPx0SrcFulrPfmMXOw==)# 配置日志
logging:level:root: infocom.heartsuit.dao: tracepattern:console: '%p%m%n'

仅去掉了:jasypt.encryptor.password=you-guess, 然后 jasypt.encryptor.password=you-guess可以换成另外两种方式传入:

  1. 通过服务启动参数
    java -jar xxx.jar --jasypt.encryptor.password=you-guess
    
  2. 通过系统环境变量
    这个可以通过Idea IDE传入(开发环境),或者实际的系统环境变量传入(生产环境)。

Jasypt的加密与解密

通过Jasypt命令行的方式生产密文密码后,可以用Jasypt提供的API进行解密,当然,也可以用API的方式来完成加密。

加密与解密

@Component
public class StringEncryptDecrypt {@AutowiredStringEncryptor encryptor;public String encrypt(String plainText) {// EncryptString encrypted = encryptor.encrypt(plainText);System.out.println("Encrypted: " + encrypted);return encrypted;}public String decrypt(String encrypted) {// DecryptString decrypted = encryptor.decrypt(encrypted);System.out.println("Decrypted: " + decrypted);return decrypted;}
}

总结

实现对配置文件敏感数据的加密,网上资源很多,但一定要注意安全性,不可以把公钥公开配置在文件中;还是开头那句话:

一把插着钥匙的锁,能说它是安全的吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/220515.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

局域网其他pc如何访问宿主机虚拟机IP?

文章目录 背景贝瑞蒲公英设置虚拟机网络连接测试 背景 使用贝瑞蒲公英异地组网&#xff0c;将家里的pc作为pgsql服务器在公司使用&#xff0c;但是虚拟机的ip和端口访问不了 贝瑞蒲公英 设置虚拟机网络 就是添加端口转发规则 连接测试 公网内其他pc连接测试 可以看到已经连接成…

教你玩转Shopify独立站!六招运营技巧不可错过

作为一个新手卖家&#xff0c;想要在 Shopify 上运营自己的独立站&#xff0c;可能会感到有些困惑。不过&#xff0c;只要掌握了一些技巧&#xff0c;就可以轻松上手。今天就为大家介绍六个运营 Shopify 独立站的技巧&#xff0c;帮助大家更好玩转 Shopify 独立站&#xff01; …

隐私计算介绍

这里只对隐私计算做一些概念性的浅显介绍&#xff0c;作为入门了解即可 目录 隐私计算概述隐私计算概念隐私计算背景国外各个国家和地区纷纷出台了围绕数据使用和保护的公共政策国内近年来也出台了数据安全、隐私和使用相关的政策法规 隐私计算技术发展 隐私计算技术安全多方计…

【vSphere | PowerCLI】使用 PowerCLI 连接 vCenter 查看 VM 故障排错

这里写目录标题 1. 连接vCenter Server2. 客户机操作系统内提供网络配置信息3. 创建VM4. 迁移VM5.故障排错连接 VC 报错&#xff1a; Error: Invalid server certificate解决方法 参考资料 1. 连接vCenter Server PS C:\Users\Administrator> Connect-VIServer 192.168.1.1…

Kubernetes (k8s) 快速认知

应用部署方式 传统部署时代 早期的时候&#xff0c;各个组织是在物理服务器上运行应用程序。缺点 资源分配问题&#xff1a; 无法限制在物理服务器中运行的应用程序资源使用 维护成本问题&#xff1a; 部署多个物理机&#xff0c;维护许多物理服务器的成本很高 虚拟化部署时…

【Qt之Quick模块】1. 概述及Quick应用程序创建流程

概述 Qt的Quick模块是用于创建现代化、动态和响应式用户界面的工具集。它是基于QML&#xff08;Qt Meta-Object Language&#xff09;和JavaScript的。 QML是一种声明性的语言&#xff0c;用于描述用户界面的结构和行为。它使用层叠样式表&#xff08;CSS&#xff09;的语法来…

轻量化压缩之【剪枝】

在深度学习领域&#xff0c;剪枝是一种常用的模型轻量化技术&#xff0c;主要是通过移除神经网络中被认为不重要的连接&#xff08;即权重&#xff09;来减少模型的大小和计算量。移除的连接可以是单独的权重&#xff08;非结构化剪枝&#xff09;&#xff0c;也可以是整个神经…

RabbitMQ入门案例

RabbitMQ 是目前比较主流的MQ消息队列中间件&#xff0c;下面简单总结RabbitMQ入门时所做的一些笔记 1.RabbitMQ 入门案例 需求&#xff1a;用 Java 编写两个程序。发送单个消息的生产者和接收消息并打印出来的消费者 1.1 添加依赖 <!--rabbitmq 依赖客户端--> <de…

美颜SDK是什么?视频美颜SDK在直播平台中的集成与接入教程详解

当下&#xff0c;主播们追求更加自然、精致的外观&#xff0c;而观众也期待在屏幕前欣赏到更为清晰、美丽的画面。为了满足这一需求&#xff0c;美颜SDK应运而生&#xff0c;成为直播平台的重要利器之一。 一、什么是美颜SDK&#xff1f; 通过美颜SDK&#xff0c;开发者可以…

三菱PLC FX3U滑动平均值滤波

三菱PLC滑动平均值滤波其它相关写法,请参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/125044013https://rxxw-control.blog.csdn.net/article/details/125044013滑动平均值滤波程序总共分为三部分,第一步为:滑动采样。 第二步为:队列求和,第三…

最强笔记生成AI —— NotionAI

NotionAI是Notion推出的一款革命性AI工具&#xff0c;它正通过利用其先进的AI技术来扩大用户群。这款强大的生成式AI工具能够帮助用户完成笔记总结、识别会议中的行动项&#xff0c;并创建和修改文本。NotionAI通过自动化枯燥的任务、为用户提供建议和模板&#xff0c;极大地简…

深度学习模型轻量化方法介绍

深度学习模型轻量化是指通过一系列技术手段减少模型的大小和计算需求&#xff0c;使其能够在资源有限的环境中&#xff08;如移动设备、嵌入式系统&#xff09;运行。下面是一些常见的模型轻量化方法&#xff1a; 模型剪枝&#xff08;Pruning&#xff09;: 描述: 模型剪枝涉及…

【Java异常】idea 报错:无效的目标发行版:17 的解决办法

【Java异常】idea 报错&#xff1a;无效的目标发行版&#xff1a;17 的解决办法 一&#xff0c;问题来源 springcloud的第一个demo项目就给我干趴了 二、原因分析 java: 无效的目标发行版: 17 原因就是 JDK 版本不对。从 IDEA 编辑器中可以找到问题的原因所在&#xff0c;…

Axure之交互与情节与一些实例

目录 一.交互与情节简介 二.ERP登录页到主页的跳转 三.ERP的菜单跳转到各个页面的跳转 四.省市联动 五.手机下拉加载 今天就到这里了&#xff0c;希望帮到你哦&#xff01;&#xff01;&#xff01; 一.交互与情节简介 "交互"通常指的是人与人、人与计算机或物体…

卷积层里的填充和步幅(padding和strides)

目录 一、填充和步幅相关概念 1、填充(padding) 2、步幅(strides) 3、总结 二、代码实现 1、填充(padding) 2、步幅(strides) 3、小结 一、填充和步幅相关概念 1、填充(padding) 当输入图片比较小的时候&#xff0c;我们一般会进行填充&#xff0c;填充是指在输入周围…

【TB作品】STM32 PWM之实现呼吸灯,STM32F103RCT6,晨启

文章目录 完整工程参考资料实验过程 实验任务&#xff1a; 1&#xff1a;实现PWM呼吸灯&#xff0c;定时器产生PWM&#xff0c;控制实验板上的LED灯亮灭&#xff1b; 2&#xff1a;通过任意两个按键切换PWM呼吸灯输出到两个不同的LED灯&#xff0c;实现亮灭效果&#xff1b; 3&…

Flink系列之:自定义函数

Flink系列之&#xff1a;自定义函数 一、自定义函数二、概述三、开发指南四、函数类五、求值方法六、类型推导七、自动类型推导八、定制类型推导九、确定性十、内置函数的确定性十一、运行时集成十二、标量函数十三、表值函数十四、聚合函数十五、表值聚合函数 一、自定义函数 …

【EI会议征稿通知】第三届区块链、信息技术与智慧金融国际学术会议 (ICBIS2024)

第三届区块链、信息技术与智慧金融国际学术会议 (ICBIS2024) The 3rd International Academic Conference on Blockchain, Information Technology and Smart Finance 第三届区块链、信息技术与智慧金融国际学术会议 (ICBIS2024) 将于2024年2月23-25日在马来西亚举行。本次会…

回归预测 | MATLAB实现GWO-DHKELM基于灰狼算法优化深度混合核极限学习机的数据回归预测 (多指标,多图)

回归预测 | MATLAB实现GWO-DHKELM基于灰狼算法优化深度混合核极限学习机的数据回归预测 &#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现GWO-DHKELM基于灰狼算法优化深度混合核极限学习机的数据回归预测 &#xff08;多指标&#xff0c;多图&#…

根据电脑硬件条件,确定Pytorch的版本?

根据CUDA确定Pytorch的版本 1 显卡型号&#xff1a;NVIDIA GeForce GTX 970 2 显卡算力&#xff1a;5.2 https://en.wikipedia.org/wiki/CUDA3 确定CUDA Runtime 4 看自己的驱动&#xff1a; CUDA Driver Version —— 12.2 nvidia-smi 5 确定使用的版本 前面3中runtime …