Springboot+mybatis-plus+dynamic-datasource+Druid數(shù)據(jù)庫(kù)配置加密
0.前言
背景
生產(chǎn)環(huán)境中, 為了保密,我們希望將數(shù)據(jù)庫(kù)密碼加密, 甚至用戶名和jdbc連接串加密。本章我們使用由苞米豆(baomidou)團(tuán)隊(duì)開(kāi)發(fā)的dynamic-datasource
多數(shù)據(jù)源組件自帶的加密工具實(shí)現(xiàn)數(shù)據(jù)庫(kù)配置加密
1. 動(dòng)態(tài)添加移除數(shù)據(jù)源
從dynamic-datasource-starter
官方給的特性說(shuō)明中,我們可以看到 dynamic-datasource-starter 支持?jǐn)?shù)據(jù)庫(kù)敏感配置信息 加密(可自定義) ENC()。
,所以我們擼一下源碼看看到底怎么實(shí)現(xiàn)的。
經(jīng)過(guò)查看源碼發(fā)現(xiàn), 該框架使用自帶的加密工具類(lèi)com.baomidou.dynamic.datasource.toolkit.CryptoUtils
進(jìn)行加解密, 且自帶有公鑰私鑰。但是源碼咋看都覺(jué)得眼熟,而且注釋是@author alibaba
,翻看druid 的加密方法 com.alibaba.druid.filter.config.ConfigTools
。原來(lái)作者直接借鑒
了Druid加密方法,之前看druid 的加密的時(shí)候發(fā)現(xiàn)公鑰和私鑰的使用反了,wenshao給的解釋是歷史原因。歷史背景和源碼咱們就聊到這,所以直接使用此工具類(lèi)加密我們的明文密碼, 然后用ENC()包裹即可實(shí)現(xiàn)加解密
。
2.基礎(chǔ)介紹
在使用 Spring Boot、MyBatis-Plus、 Druid 進(jìn)行數(shù)據(jù)庫(kù)配置時(shí),如果需要對(duì)敏感配置信息進(jìn)行加密,可以通過(guò)以下方式實(shí)現(xiàn):
- 使用加密算法對(duì)敏感信息進(jìn)行加密,如數(shù)據(jù)庫(kù)的用戶名、密碼等信息??梢赃x擇對(duì)稱加密算法(如AES)或非對(duì)稱加密算法(如RSA)進(jìn)行加密。
- 將加密后的敏感信息保存在安全的位置,如配置文件或系統(tǒng)環(huán)境變量。
- 在應(yīng)用啟動(dòng)時(shí),通過(guò)配置文件或環(huán)境變量讀取加密后的敏感信息。
- 在配置動(dòng)態(tài)數(shù)據(jù)源時(shí),使用解密的敏感信息進(jìn)行數(shù)據(jù)庫(kù)配置。
這是基本的思路,本章我們不造輪子,直接使用國(guó)產(chǎn)優(yōu)秀框架baomidou團(tuán)隊(duì)的工具 dynamic-datasource
多數(shù)據(jù)源組件自帶的加密工具實(shí)現(xiàn)數(shù)據(jù)庫(kù)配置加密
3. 使用步驟示例
簡(jiǎn)單方式,使用默認(rèn)的加密
1. 使用下面 工具類(lèi)輸出,加密后的密碼
import com.baomidou.dynamic.datasource.toolkit.CryptoUtils;
public class DBCryptoUtils extends CryptoUtils{
// 第一種方式 使用默認(rèn)key 加密解密
public static void test1( ) throws Exception {
System.out.println("-------------------------------------默認(rèn)加密-------------------------------------");
String password = "abc123";
String encodePassword = CryptoUtils.encrypt(password);
System.out.println("加密后密碼:"+CryptoUtils.encrypt(password));
}
// 第二種方式 使用自定義key,強(qiáng)烈建議
public static void test2( ) throws Exception {
System.out.println("-------------------------------------自定義key-------------------------------------");
String[] pair = CryptoUtils.genKeyPair(512);
System.out.println("privateKey: " + pair[0]);
System.out.println("publicKey: " + pair[1]);
// 按道理應(yīng)該用公鑰加密,私鑰解密,作者直接抄的druid 的,把這個(gè)不規(guī)范的寫(xiě)法也給抄過(guò)來(lái)了,不影響效果,只覺(jué)得怪怪的。
System.out.println("加密后密碼: " + CryptoUtils.encrypt(pair[0], "abc123"));
}
public static void main(String[] args) throws Exception {
test1();
test2();
}
}
1. 將上面加密后的密碼配置到配置文件中
如果使用的默認(rèn)key,即上面生成加密后密碼的第一種,則使用下面方式配置
spring:
datasource:
dynamic:
#有默認(rèn)值可以不配置,強(qiáng)烈建議更換
public-key:
datasource:
master:
url: DB地址
username: 用戶名
#配置加密后的密碼
password: ENC(Ue9QTmtvOX8XMdRIZVqUAbmbLNfAjQQO9jokfVEfaew+HFGZPndSmcq2pOTS2xuC7Pg/z1gUGS82HOmWw0d9Cw==)
driver-class-name: com.mysql.jdbc.Driver# 驅(qū)動(dòng)保持jdbc url一致
public-key: #在多數(shù)據(jù)源下每個(gè)數(shù)據(jù)源可以獨(dú)立設(shè)置,沒(méi)有就繼承上面的。
如果使用的自定義的key,即上面既生成publicKey和privateKey 以及加密后密碼的第2種方式,則使用下面方式配置
spring:
datasource:
dynamic:
# 配置上面輸出的 public key
public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJirfs9pc4fsDdXqjMto4zY+sYZ7d/XYwIQIYqj2FoqxvVC61tjKtG12nMSlwgXbV+DNpWh9W76QjM2XCNYB6VUCAwEAAQ==
datasource:
master:
url: DB地址
username: 用戶名
#配置加密后的密碼 根據(jù)上面生成的秘鑰,加密后的密碼
password: ENC(BSbigK5YuTXLOUDekSm3uU+h/n2/rIwa4DxQWPbfuhf9irwoakQy777AYHqJVz/WEG5BTFp4Ym+lguH3o+f4kQ==)
driver-class-name: com.mysql.jdbc.Driver# 驅(qū)動(dòng)保持jdbc url一致
public-key: #在多數(shù)據(jù)源下每個(gè)數(shù)據(jù)源可以獨(dú)立設(shè)置,沒(méi)有就繼承上面的。
到這兒基本上就OK了,如果想知道源碼是怎么執(zhí)行的可以繼續(xù)向下看
4. 官方源碼分析
5.1. 解密的核心源碼
我們先來(lái)看EncDataSourceInitEvent
源碼.我們可以看到EncDataSourceInitEvent
類(lèi),實(shí)現(xiàn)了DataSourceInitEvent
接口。該類(lèi)用于在數(shù)據(jù)源初始化之前對(duì)數(shù)據(jù)源屬性進(jìn)行解密操作。
/**
* 多數(shù)據(jù)源默認(rèn)解密事件
*
* @author TaoYu
*/
@Slf4j
public class EncDataSourceInitEvent implements DataSourceInitEvent {
/**
* 加密正則
*/
private static final Pattern ENC_PATTERN = Pattern.compile("^ENC\\((.*)\\)$");
@Override
public void beforeCreate(DataSourceProperty dataSourceProperty) {
String publicKey = dataSourceProperty.getPublicKey();
if (StringUtils.hasText(publicKey)) {
dataSourceProperty.setUrl(decrypt(publicKey, dataSourceProperty.getUrl()));
dataSourceProperty.setUsername(decrypt(publicKey, dataSourceProperty.getUsername()));
dataSourceProperty.setPassword(decrypt(publicKey, dataSourceProperty.getPassword()));
}
}
@Override
public void afterCreate(DataSource dataSource) {
}
/**
* 字符串解密
*/
private String decrypt(String publicKey, String cipherText) {
if (StringUtils.hasText(cipherText)) {
Matcher matcher = ENC_PATTERN.matcher(cipherText);
if (matcher.find()) {
try {
return CryptoUtils.decrypt(publicKey, matcher.group(1));
} catch (Exception e) {
log.error("DynamicDataSourceProperties.decrypt error ", e);
}
}
}
return cipherText;
}
}
DataSourceInitEvent
接口 又是干什么用的呢,我們點(diǎn)進(jìn)去可以看到
DataSourceInitEvent是 baomidou的dynamic.datasource組件 位于com.baomidou.dynamic.datasource.event
包下定義的一個(gè)鉤子接口,在創(chuàng)建連接池前和后可以搞事情。
- 該接口定義了兩個(gè)方法:
beforeCreate
和afterCreate
,用于在連接池創(chuàng)建前后執(zhí)行一些操作。 -
beforeCreate
方法接受一個(gè)DataSourceProperty
參數(shù),用于傳遞數(shù)據(jù)源的基本信息。 -
afterCreate
方法接受一個(gè)DataSource
參數(shù),表示已創(chuàng)建的連接池對(duì)象。
總之,這段代碼是一個(gè)事件接口,用于在多數(shù)據(jù)源連接池創(chuàng)建過(guò)程中執(zhí)行一些自定義操作。
讓我們逐行解析代碼的功能:
-
定義了一個(gè)名為
ENC_PATTERN
的正則表達(dá)式模式,用于匹配加密字符串的格式。 -
實(shí)現(xiàn)了
beforeCreate
方法,該方法在數(shù)據(jù)源創(chuàng)建之前調(diào)用。它接收一個(gè)DataSourceProperty
對(duì)象作為參數(shù),表示數(shù)據(jù)源的屬性。 -
在
beforeCreate
方法中,首先獲取數(shù)據(jù)源屬性中的公鑰(publicKey)。如果公鑰存在,表示需要進(jìn)行解密操作。 -
接下來(lái),使用公鑰對(duì)數(shù)據(jù)源的URL、用戶名和密碼進(jìn)行解密操作。調(diào)用
decrypt
方法對(duì)這些屬性進(jìn)行解密,并將解密后的值設(shè)置回dataSourceProperty
對(duì)象中。 -
實(shí)現(xiàn)了
afterCreate
方法,該方法在數(shù)據(jù)源創(chuàng)建之后調(diào)用。在該方法中可以執(zhí)行一些額外的操作,但是該方法在給定的代碼中沒(méi)有實(shí)現(xiàn)任何邏輯。 -
定義了一個(gè)私有方法
decrypt
,用于對(duì)加密字符串進(jìn)行解密。該方法接收公鑰和密文作為參數(shù),并返回解密后的明文字符串。 -
在
decrypt
方法中,首先判斷密文是否符合加密格式(通過(guò)ENC_PATTERN
進(jìn)行匹配)。如果匹配成功,就使用給定的公鑰和密文調(diào)用CryptoUtils.decrypt
方法進(jìn)行解密操作。
總結(jié)起來(lái),EncDataSourceInitEvent
類(lèi)實(shí)現(xiàn)了DataSourceInitEvent
接口,用于在數(shù)據(jù)源初始化之前對(duì)數(shù)據(jù)源屬性進(jìn)行解密操作。它通過(guò)正則表達(dá)式匹配加密字符串的格式,并使用給定的公鑰對(duì)密文進(jìn)行解密。解密后的明文值將用于設(shè)置數(shù)據(jù)源的URL、用戶名和密碼屬性。
5.2. 自定義解密
到這兒我們可能,已經(jīng)知道怎么自定義解密,無(wú)外乎就是實(shí)現(xiàn)DataSourceInitEvent
接口的beforeCreate
方法然后自定義處理。但是我們自定義的這個(gè)實(shí)現(xiàn)類(lèi)和官方默認(rèn)的解密實(shí)現(xiàn)類(lèi)優(yōu)先級(jí)怎么搞呢,其實(shí)官方在DynamicDataSourceAutoConfiguration
配置類(lèi)中已經(jīng)使用了@condition條件注解。滿足優(yōu)先使用我們自定義的加密的實(shí)現(xiàn)類(lèi)了。我們只需要交給Spring 容器就OK.文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-763086.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-763086.html
/**
* 自定義多數(shù)據(jù)源默認(rèn)解密事件
*/
@Slf4j
@Configuration
public class CustomeEncDataSourceInitEvent implements DataSourceInitEvent {
/**
* 加密正則 實(shí)現(xiàn)自定義的 例如BCC
*/
private static final Pattern ENC_PATTERN = Pattern.compile("^BCC\\((.*)\\)$");
@Override
public void beforeCreate(DataSourceProperty dataSourceProperty) {
// TODO 實(shí)現(xiàn)自定義的解密
String publicKey = dataSourceProperty.getPublicKey();
if (StringUtils.hasText(publicKey)) {
dataSourceProperty.setUrl(decrypt(publicKey, dataSourceProperty.getUrl()));
dataSourceProperty.setUsername(decrypt(publicKey, dataSourceProperty.getUsername()));
dataSourceProperty.setPassword(decrypt(publicKey, dataSourceProperty.getPassword()));
}
}
@Override
public void afterCreate(DataSource dataSource) {
}
/**
* 字符串解密
*/
private String decrypt(String publicKey, String cipherText) {
if (StringUtils.hasText(cipherText)) {
Matcher matcher = ENC_PATTERN.matcher(cipherText);
if (matcher.find()) {
try {
return CryptoUtils.decrypt(publicKey, matcher.group(1));
} catch (Exception e) {
log.error("DynamicDataSourceProperties.decrypt error ", e);
}
}
}
return cipherText;
}
}
5. 參考資料
- dynamic-datasource GitHub 倉(cāng)庫(kù) ↗:dynamic-datasource 的官方 GitHub 倉(cāng)庫(kù),包含源代碼、文檔和示例等資源。
到了這里,關(guān)于Springboot+dynamic-datasource+Druid數(shù)據(jù)庫(kù)配置加密的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!