修復 Java-MySQL 連線異常:不允許檢索公鑰
1. 概述
將 Java 應用程式連接到 MySQL 8 資料庫時,我們可能會遇到一個常見的例外:
Public Key Retrieval is not allowed
上述訊息並非表示存在遺留漏洞,而是現代 MySQL 和 MySQL Connector/J 的安全保護措施。它通常發生在驗證期間,當 JDBC 驅動程式無法安全地檢索 MySQL 伺服器的 RSA 公鑰時。
在本教程中,我們將了解與Public Key Retrieval is not allowed 」訊息相關的各個方面:
- 錯誤何時發生以及原因如何
- 如何在現代 MySQL 環境中重現此問題
- 有哪些錯誤修復方案?
讓我們逐一探討以上幾點。
2. 錯誤發生的時間和原因
MySQL 8.0.4及更高版本使用caching_sha2_password作為新的預設驗證外掛程式。至關重要的是,該插件比舊版本更加安全。
當用戶端在未使用 SSL 的情況下連線時,新的驗證方法需要使用 RSA 公鑰進行密碼交換。此密鑰用於在交換過程中加密密碼。出於安全考慮,MySQL Connector/J 不允許自動取得公鑰,除非我們明確設定。
通常情況下,當滿足以下某些條件時就會出現錯誤:
- 身份驗證使用
caching_sha2_password - SSL 已關閉或無法使用
- 未明確允許取得公鑰。
因此,驅動程式阻止了身份驗證過程,並拋出了Public Key Retrieval is not allowed異常。
3. 重現錯誤
我們來嘗試在本機機器環境中重現該錯誤。
3.1 測試設置
在這個配置中,我們使用了兩台機器。一台機器上安裝了Java,另一台機器上運行MySQL伺服器。
我們姑且稱它們為A和B:
- 機器 A:MySQL 8 伺服器,IP 位址為
192.168.29.116 - 機器 B:IP 位址為
192.168.29.21的 Java 用戶端
之後,我們設定 MySQL 伺服器。
3.2 MySQL 伺服器配置
讓我們確保 MySQL 監聽所有介面。
為此,我們需要編輯機器 A 上的 MySQL 設定檔:
$ sudo cat /etc/mysql/mysql.conf.d/mysqld.cnf
...
bind-address = 0.0.0.0
...
之後,我們重啟MySQL:
$ sudo systemctl restart mysql
現在,一切都應該準備就緒了。
3.3 範例資料庫
為了進行設置,我們建立資料庫mydb和使用者testuser :
mysql> CREATE DATABASE mydb;
mysql> CREATE USER 'testuser'@'%'IDENTIFIED BY 'testpass';
mysql> GRANT ALL PRIVILEGES ON mydb.* TO 'testuser'@'%';
FLUSH PRIVILEGES;
新用戶現在擁有對資料庫mydb所有權限。此外,身份驗證使用預設的 MySQL 8 驗證外掛程式:
mysql > SELECT user, host, plugin FROM mysql.user WHERE user='testuser';
插件類型必須為caching_sha2_password 。
3.4. 使用 Java 測試程式碼觸發錯誤
讓我們來嘗試在機器 B 上觸發這個錯誤。
為此,我們嘗試使用標準 JDBC 連線字串連線到 MySQL 8 實例。
具體來說,我們嘗試建立不含 SSL 或特定 RSA 設定的連線:
import java.sql.Connection;
import java.sql.DriverManager;
public class TestConnection {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://192.168.29.116:3306/mydb"+"?useSSL=false"+"&allowPublicKeyRetrieval=false";
Connection conn = DriverManager.getConnection(
url, "testuser", "testpass"
);
System.out.println("Connected successfully");
conn.close();
}
}
在上面的範例中, testuser使用了caching_sha2_password 。此外,我們明確地禁用了 SSL (useSSL=false) 。驅動程式嘗試取得公鑰。但是,由於預設的安全性策略,請取得失敗並拋出異常。
運行上述程式碼後,我們可以看到預期的錯誤:
com.mysql.cj.exceptions.InvalidConnectionAttributeException:
Public Key Retrieval is not allowed
因此,我們可以驗證實驗室裝置運作正常。
4. 錯誤修復
根據我們已有的設置,我們可以得出以下幾點:
- 該異常並不表示憑證錯誤。
- 此認證方法需要 RSA 加密
- 驅動程式拒絕自動取得公鑰。
因此,即使憑證正確,身份驗證方法也不相容。
針對此錯誤有幾種解決方法:
- 允許公鑰檢索(僅限開發模式)
- 啟用 SSL(安全方式)
- 更換身份驗證插件(最後的手段)
然而,是否選擇使用修復方法取決於特定環境。
5. 允許公鑰檢索(僅限開發)
解決此錯誤的一個快速方法是明確啟用公鑰檢索。
我們可以透過在連接 URL 中新增allowPublicKeyRetrieval=true字串來實現這一點:
"jdbc:mysql://192.168.29.116:3306/mydb"+"?useSSL=false"+"&allowPublicKeyRetrieval=true";
現在,驅動程式可以從伺服器取得 RSA 公鑰。
然而,這種方法僅適用於本地測試。至關重要的是,在生產環境中,如果不使用 SSL,這種方法會降低安全性。
6. SSL 設定及憑證驗證
如果設定 SSL,我們應該可以獲得更安全、更持久的解決方案。讓我們看看如何在 MySQL 伺服器上正確設定 SSL。
6.1. 在 MySQL 伺服器上啟用 SSL
首先,我們建立一個用於存放 MySQL SSL 檔案的目錄:
$ sudo mkdir -p /etc/mysql/ssl
上述目錄儲存了 MySQL 使用的所有憑證和私鑰。
接下來,我們切換到新目錄:
$ cd /etc/mysql/ssl
所有文件現在都保存在這裡,這有助於保護敏感金鑰。
MySQL 需要憑證授權單位 (CA) 來驗證伺服器憑證。
讓我們產生一個 CA 私鑰:
$ sudo sh -c 'openssl genrsa 2048 > ca-key.pem'
ca-key.pem是一個私鑰,充當信任根。
接下來,我們使用ca-key.pem建立一個自簽名 CA 憑證:
$ sudo openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca.pem
在這種情況下,自簽名憑證ca.pem用於識別 CA。稍後,我們也使用它來簽署 MySQL 伺服器憑證。
接下來,我們還需要兩把鑰匙:
-
server-key.pem:伺服器的私鑰 -
server-req.pem:憑證簽署請求 (CSR) 的金鑰
讓我們創建這兩個密鑰:
$ sudo openssl req -newkey rsa:2048 -days 3650 -nodes \
-keyout server-key.pem -out server-req.pem \
-subj "/C=US/ST=CL/L=NewYork/CN=192.168.29.116"
現在,我們可以使用 CA 對伺服器憑證進行簽署:
$ sudo openssl x509 -req -in server-req.pem -days 3650 \
-CA ca.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
最後,我們取得了受信任的伺服器憑證server-cert.pem 。客戶端可以使用 CA 憑證來驗證伺服器。
讓我們保護好SSL金鑰:
$ sudo chmod 600 *.pem
$ sudo chown mysql:mysql *.pem
這樣一來,只有 MySQL 才能存取它們。
6.2 設定 MySQL 使用 SSL
取得憑證後,我們需要設定 MySQL 以使用它們。為此,我們需要在 MySQL 設定檔的[mysqld]部分下新增 SSL 金鑰路徑。
讓我們打開mysqld.cnf檔案並進行修改:
$ sudo cat /etc/mysql/mysql.conf.d/mysqld.cnf
...
ssl-ca = /etc/mysql/ssl/ca.pem
ssl-cert = /etc/mysql/ssl/server-cert.pem
ssl-key = /etc/mysql/ssl/server-key.pem
...
為了讓變更生效,我們需要重新啟動 MySQL:
$ sudo systemctl restart mysql
讓我們透過檢查 MySQL 內部目前的 SSL 配置來驗證 SSL 是否已啟動:
mysql > SHOW VARIABLES LIKE '%ssl%';
同樣,我們也可以檢查 MySQL 運行時狀態:
mysql > SHOW GLOBAL STATUS LIKE 'Ssl_%';
如果在輸出中看到have_ssl = YES和 SSL 文件,則表示 MySQL 正在使用 SSL 運行。
6.3. 將伺服器憑證複製到客戶端
為了使 Java 用戶端能夠透過 SSL 與 MySQL 伺服器通信,雙方都需要相互信任。為此,我們使用 CA 憑證。
所以,我們將ca.pem從伺服器複製到 Java 用戶端機器。具體來說,我們透過scp將其複製到/tmp目錄:
$ sudo scp /etc/mysql/ssl/ca.pem [email protected]:/tmp/ca.pem
該檔案應放在 Java 客戶端的/tmp目錄下。
6.4. 在客戶端建立和驗證 Java 信任庫
Java 使用信任函式庫來驗證 SSL 憑證。透過自訂信任庫,我們可以避免修改系統範圍的 JVM 配置。
讓我們建立一個新的信任庫,並將 CA 憑證ca.pem匯入其中:
$ keytool -import -alias mysqlCA -keystore /tmp/mysql-truststore.jks -file /tmp/ca.pem \
-storepass changeit -noprompt
因此,JVM 現在可以信任 MySQL 伺服器進行 SSL 握手。
讓我們驗證信任庫:
$ keytool -list -v -keystore /tmp/mysql-truststore.jks -storepass changeit
輸出結果列出了憑證別名、類型和 CA 詳細資訊。
所以,我們現在可以使用 Java 信任函式庫來建立與 MySQL 的 SSL 連線。
6.5. 使用 SSL 的 Java 程式碼
信任庫準備好後,我們將更新 Java 程式碼以使用 SSL:
System.setProperty("javax.net.ssl.trustStore", "/tmp/mysql-truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
這些屬性將 JVM 指向受信任的憑證。
最後,我們更新 JDBC 連線 URL:
String url = "jdbc:mysql://192.168.29.116:3306/mydb"+"?useSSL=true"+"&sslMode=VERIFY_CA"+"&allowPublicKeyRetrieval=true";
以上每個參數都有其特定用途:
-
useSSL=true:啟用 SSL -
sslMode=VERIFY_CA:檢查伺服器憑證是否與憑證授權單位 (CA) 的憑證相符。 -
allowPublicKeyRetrieval=true:允許在需要時安全地檢索伺服器公鑰。
最後,當我們執行 Java 程式碼時,不應該出現Public Retrieval錯誤。
8. 更改身份驗證插件
我們也可以支援未使用caching_sha2_password舊版用戶端。在這種情況下,我們會將使用者切換到舊版的mysql_native_password驗證外掛。
為此,我們在 MySQL 提示字元下使用ALTER命令:
mysql > ALTER USER 'testuser'@'%' IDENTIFIED WITH mysql_native_password BY 'testpass';
mysql > FLUSH PRIVILEGES;
因此,我們不需要RSA金鑰交換。然而,這種方法安全性較低,因為它依賴較舊的身份驗證插件。
9. 結論
本文探討了Public Key Retrieval is not allowed錯誤的原因以及如何解決該錯誤。
總的來說,使用 SSL 來修復此類錯誤應該是最佳選擇,因為它提供了更安全的身份驗證方法。