阿里巴巴Druid,輕鬆實現MySQL資料庫連線加密!
阿新 • • 發佈:2021-03-02
## 為什麼要加密?
現在的開發習慣,無論是公司的專案還是個人的專案,都會選擇將原始碼上傳到 Git 伺服器(GitHub、Gitee 或是自建伺服器),但只要**將原始碼提交到公網伺服器就會存在原始碼洩漏的風險**,而資料庫配置資訊作為原始碼的一部分,一旦出現原始碼洩漏,那麼資料庫中的所有資料都會公之於眾,其產生的不良後果無法預期(比如某某酒店的資訊)。
於是**為了避免這種問題的產生,我們至少要對資料庫的密碼進行加密操作**,這樣即使得到了原始碼,也不會造成資料的洩露,也算保住了最後一塊遮羞布。
## 如何加密?
對於 Java 專案來說,要想快速實現資料庫的加密,**最簡單可行的方案就是使用阿里巴巴提供的 Druid 來實現加密**。
#### 什麼是Druid?
Druid(中文譯為“德魯伊”)是阿里巴巴開源的一款 Java 語言中最好的資料庫連線池。Druid 提供了強大的監控和擴充套件功能,當然也包含了資料庫的加密功能。
Druid 開源地址:[https://github.com/alibaba/druid/](https://github.com/alibaba/druid/)
#### Druid可以做什麼?
1. Druid 可以監控資料庫訪問效能,Druid 內建提供了一個功能強大的 StatFilter 外掛,能夠詳細統計 SQL 的執行效能,這對於線上分析資料庫訪問效能有幫助。
1. 替換資料庫連線池 DBCP 和 C3P0,Druid 提供了一個高效、功能強大、可擴充套件性好的資料庫連線池。
1. 資料庫密碼加密,直接把資料庫密碼寫在配置檔案中,這是不好的行為,容易導致安全問題。DruidDruiver 和 DruidDataSource 都支援 PasswordCallback。
1. SQL 執行日誌,Druid 提供了不同的 LogFilter,能夠支援 Common-Logging、Log4j 和 JdkLog,你可以按需要選擇相應的 LogFilter,監控你應用的資料庫訪問情況。
1. 擴充套件 JDBC,如果你要對 JDBC 層有程式設計的需求,可以通過 Druid 提供的 Filter-Chain 機制,很方便編寫 JDBC 層的擴充套件外掛。
對於本文來說,我們重點來看它的第 3 個特性,也就是使用 Druid 來實現資料庫密碼加密。
## 加密執行流程
在沒有進行密碼加密之前,專案的互動流程是這樣的:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/92791/1614589542046-9cae3b9a-c514-4d43-945a-406de98418c6.png#align=left&display=inline&height=145&margin=%5Bobject%20Object%5D&name=image.png&originHeight=290&originWidth=888&size=29807&status=done&style=none&width=444)
在使用了密碼加密之後,專案的互動流程就變成了這樣:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/92791/1614587616737-76dc5e08-a773-4437-a854-120798103661.png#align=left&display=inline&height=276&margin=%5Bobject%20Object%5D&name=image.png&originHeight=551&originWidth=977&size=48429&status=done&style=none&width=488.5)
## 使用Druid實現加密
本示例執行環境:
> Spring Boot 2.4.3
>
> MySQL 5.7
>
> Java 1.8
>
> Idea 2020.1.3
### 1.新增Druid依賴
Maven 專案:
```xml
com.alibaba
druid-spring-boot-starter
1.2.5
```
Gradle 專案:
```xml
compile 'com.alibaba:druid-spring-boot-starter:1.2.5'
```
獲取 Druid 最新版本:[https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter](https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter)
### 2.生成密文
Druid 新增完成之後就可以藉助 Druid 中提供的 `ConfigTools` 類來加密密碼了,實現程式碼如下:
```java
import com.alibaba.druid.filter.config.ConfigTools;
class MyTests {
public static void main(String[] args) throws Exception {
// 需要加密的明文命名
String password = "youPassword"; // 【注意:這裡要改為你自己的密碼】
// 呼叫 druid 生成私鑰、公鑰、密文
ConfigTools.main(new String[]{password});
}
}
```
以上程式碼執行的結果如下:
> privateKey:MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEApOjcMWSDzJiKVGmtcBBoQPtM9tVW2H2cnS6xZK7NrbzQXYWLQD2zefIrrx9vMvqRIHEqkmAHTuUcUXHgCxu0cwIDAQABAkAlqo5ItdWo0Jqf5zdXJlg5p2yP4HCiqCYyfKzF+2s9KEmgWZJWTctZDsgQ0iYUohORR59I+J4nabhel1x5/INpAiEA6jwSyFqMUPOh1XlrzNFek+RthOQ5n4+ALPo+vULayO0CIQC0O7JM9sIq+tg+jCGv+ypk6vbuRKY9m5W2rSRXapGm3wIgRHul3jAjIDPrF/f1HaAFL+Y0Yws7Ebyp8/yCRWF7iA0CIALbe20q8FMcHPeI4zPWCIsHCpkmb3hEkjAOOKhGIT8DAiAqiUuz92NqKeyjmOfons1ka65EzVwA3NDhZ6+IQcnuig==
> publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKTo3DFkg8yYilRprXAQaED7TPbVVth9nJ0usWSuza280F2Fi0A9s3nyK68fbzL6kSBxKpJgB07lHFFx4AsbtHMCAwEAAQ==
> password:IMgKm27bOHok3/+5aDL4jGBoVVZkpicbbM6pIXQppi3dI7h3jngSAqhqwqYnfuYpyVJ0k++q9xWWnHtd6sAWnQ==
從上述結果可以看出,使用 `ConfigTools` 類會生成 3 部分的內容:
1. privateKey:私鑰,暫時不會用到,用於密碼的加密;
1. publicKey:公鑰,用於密碼的解密;
1. password:加密之後的密碼。
> PS:要實現資料庫的加密,主要使用的是 publicKey(公鑰)和 password(密文),這就把明文轉換成密文了。
### 3.新增配置
完成了以上操作之後,只需要將上一步生成的**公鑰**和**密文**新增到專案的配置檔案 application.yml(或application.xml)中就實現了加密操作了,具體配置資訊如下:
```xml
spring:
# MySQL 配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=false
username: root
password: IMgKm27bOHok3/+5aDL4jGBoVVZkpicbbM6pIXQppi3dI7h3jngSAqhqwqYnfuYpyVJ0k++q9xWWnHtd6sAWnQ==
# encrypt config
filters: config
connect-properties:
config.decrypt: true
config.decrypt.key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKTo3DFkg8yYilRprXAQaED7TPbVVth9nJ0usWSuza280F2Fi0A9s3nyK68fbzL6kSBxKpJgB07lHFFx4AsbtHMCAwEAAQ==
```
其中 password 對應的是上一步生成的 password(密文),而 config.decrypt.key 對應的是上一步生成的 publicKey(公鑰),如下圖所示:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/92791/1614591148052-e554f420-4fc1-4951-aca4-2e9383e2ef2d.png#align=left&display=inline&height=293&margin=%5Bobject%20Object%5D&name=image.png&originHeight=586&originWidth=1741&size=157953&status=done&style=none&width=870.5)
這裡提供一個原始的配置檔案,以便和加密後的配置檔案進行比對:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/92791/1614592253450-ae1aba93-1bd8-4fe9-9f46-1f6820872bef.png#align=left&display=inline&height=149&margin=%5Bobject%20Object%5D&name=image.png&originHeight=298&originWidth=1063&size=52755&status=done&style=none&width=531.5)
### 4.注意事項-插著鑰匙的鎖
經過前面 3 步的配置之後,我們的程式就可以正常運行了,但這遠沒有結束!
在第 3 步配置時,我們將密文和公鑰都寫入配置檔案,這就會造成**當有人拿到密文和公鑰之後,就可以使用 Druid 將加密的密碼還原出來了**,這就好比一把插著鑰匙的鎖是極不安全的。
因此我們**正確的使用姿勢:是將公鑰找一個安全的地方儲存起來,每次在專案啟動時動態的將公鑰設定到專案中**,這樣就可以有效的保證密碼的安全了。
#### 正確的配置檔案
接下來我們將 Spring Boot 的公鑰設定為配置項,在專案執行時再替換為具體的值,最終的安全配置資訊如下:
```xml
spring:
# MySQL 配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=false
username: root
password: IMgKm27bOHok3/+5aDL4jGBoVVZkpicbbM6pIXQppi3dI7h3jngSAqhqwqYnfuYpyVJ0k++q9xWWnHtd6sAWnQ==
# encrypt config
filters: config
connect-properties:
config.decrypt: true
config.decrypt.key: ${spring.datasource.druid.publickey}
```
可以看出公鑰被修改成“${spring.datasource.druid.publickey}”了,這就相當於使用佔位符先把坑給占上,等專案啟動時再更換上具體的值。
> PS:“spring.datasource.druid.publickey”並非是固定不可變的 key,此 key 值使用者可自行定義。
#### 開發環境替換公鑰
開發環境只需要在 Idea 的啟動引數中配置公鑰的值即可,如下圖所示:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/92791/1614606760000-3c26def6-dc40-4a5d-a283-d72432138234.png#align=left&display=inline&height=296&margin=%5Bobject%20Object%5D&name=image.png&originHeight=591&originWidth=1274&size=60852&status=done&style=none&width=637)
當我們輸入正確的公鑰值時程式可以正常執行,當輸入一個錯誤的公鑰值時就會提示解碼失敗,如下圖所示:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/92791/1614582003747-eadaa583-831a-4928-87af-8056a413946c.png#align=left&display=inline&height=213&margin=%5Bobject%20Object%5D&name=image.png&originHeight=425&originWidth=1812&size=137631&status=done&style=none&width=906)
#### 生產環境替換公鑰
生產環境在啟動 jar 包時只需要動態設定公鑰的值即可,參考以下命令:
> java -jar xxx.jar --spring.datasource.druid.publickey=你的公鑰
## Druid執行原理
經過上述步驟之後,我們就完成 MySQL 密碼的加密了,這樣當 Spring Boot 專案啟動時,**Druid 的攔截器會使用密文和公鑰將密碼還原成真實的密碼以供專案使用**,當然這一切都無需人工干預(無需編寫任何程式碼),Druid 已經幫我封裝好了,我們只需要通過以上配置即可。
什麼?你想知道 Druid 是如何通過密文和公鑰還原出真實的密碼的?
沒問題,滿足你,其實 `ConfigTools` 類中已經提供了相應實現,程式碼如下:
```java
// 密文
String password = "VwH1mu2IUpqjfKTd+gSikiZgJTi+3Y5zFIFRfxYnH1UqHzm1K8TIHnMaV3TErBaGsVEaGV0e63pb0Ys3Wdm7Kg==";
// 公鑰
String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALWIEp19IM04sB+vQXnEOH9gFNFdL5TFGSEhORgHj4MnfTfBSNaOoSgCaM8BOpjiHmwuEb7LpvmXI1x/ymUvNzECAwEAAQ==";
// 還原成真實的密碼
String result = ConfigTools.decrypt(publicKey, password);
System.out.println("最終結果:" + result);
```
## 總結
本文我們使用阿里巴巴開源的 Druid 實現了 MySQL 的密碼加密,Druid 的加密過程非常方便,無需編寫任何程式碼,只需要新增 Druid 依賴,再通過 Druid 的工具類生成密文,最後將密文配置到 application.yml 檔案即可。專案在執行時會通過攔截器將密文轉換成真正的密碼,從而實現了 MySQL 密碼的加密和解碼的過程。
#### 最後
原創不易,如果覺得本文對你有幫助,請點個贊再走唄。
> 關注公眾號「Java中文社群」檢視更多 Druid