SSL 憑證問題診斷與解決方案
問題描述
當呼叫 restTemplateSent API 時,遇到 SSL 憑證驗證失敗錯誤:
PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
已實施的改進
1. 增強的錯誤日誌記錄
在 RestTemplateSender 類別中新增了 logSSLCertificateError() 方法,當發生 SSL 憑證錯誤時會自動記錄詳細資訊:
- 錯誤類型識別: 自動識別 SSL Handshake 失敗、憑證過期、憑證尚未生效等問題
- 根本原因分析: 追蹤異常鏈找到根本原因
- 詳細診斷資訊: 包含目標 URL、錯誤訊息、可能原因
- 解決方案建議: 提供具體的修復步驟和指令
2. 日誌輸出範例
當發生 SSL 憑證錯誤時,系統會輸出如下格式的日誌:
═══════════════════════════════════════════════════════
SSL 憑證驗證失敗 - 目標 URL: https://example.text.xxx.com/...
═══════════════════════════════════════════════════════
錯誤類型: SSL Handshake 失敗
錯誤訊息: PKIX path validation failed...
→ 憑證路徑驗證失敗 (PKIX path validation failed)
→ 可能原因:
1. 伺服器憑證不被信任 (憑證未在 Java truststore 中)
2. 憑證鏈不完整 (缺少中繼憑證)
3. 憑證已過期或尚未生效
4. 憑證的主體名稱與域名不符
→ 憑證有效性檢查失敗
→ 憑證可能已過期或尚未生效
═══════════════════════════════════════════════════════
建議解決方案:
1. 使用 openssl 獲取伺服器憑證:
openssl s_client -connect example.text.xxx.com:443 -showcerts
2. 將憑證匯入 Java truststore:
keytool -import -alias example.text.xxx.com -file cert.pem -keystore $JAVA_HOME/lib/security/cacerts
3. 或在測試環境中暫時停用 SSL 驗證 (不建議用於生產環境)
4. 檢查 Java truststore 位置: /path/to/java/lib/security/cacerts
5. 目前使用的 Java 版本: 11.0.x
═══════════════════════════════════════════════════════
診斷工具
使用 PowerShell 腳本獲取憑證
執行以下指令來獲取並診斷伺服器憑證:
.\scripts\get-ssl-cert.ps1 -hostname example.text.xxx.com
此腳本會: 1. 連接到目標伺服器並獲取 SSL 憑證 2. 顯示憑證的詳細資訊(發行者、有效期限等) 3. 檢查憑證是否過期 4. 將憑證儲存到本地檔案 5. 提供將憑證匯入 Java truststore 的完整指令
手動獲取憑證(使用 OpenSSL)
如果已安裝 OpenSSL,可以執行:
# 獲取憑證
openssl s_client -connect example.text.xxx.com:443 -showcerts > cert.pem
# 查看憑證資訊
openssl x509 -in cert.pem -text -noout
# 檢查憑證有效期
openssl x509 -in cert.pem -noout -dates
解決方案
方案 1: 匯入憑證到 Java Truststore(推薦)
-
獲取憑證(使用上述工具)
-
匯入憑證到 Java truststore(需要管理員權限):
keytool -import -alias example.text.xxx.com \
-file cert.pem \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit
Windows PowerShell:
keytool -import -alias example.text.xxx.com `
-file cert.pem `
-keystore "$env:JAVA_HOME\lib\security\cacerts" `
-storepass changeit
- 驗證憑證已匯入:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit | grep example.text.xxx.com
- 重新啟動應用程式
方案 2: 使用自訂 Truststore
在 application.properties 中配置:
# 指定自訂 truststore
server.ssl.trust-store=classpath:truststore.jks
server.ssl.trust-store-password=your-password
server.ssl.trust-store-type=JKS
方案 3: 停用 SSL 驗證(僅限開發/測試環境,不建議)
修改 RestTemplateConfiguration.java,配置 RestTemplate 忽略 SSL 驗證:
// ⚠️ 警告: 僅用於開發環境,生產環境切勿使用!
@Bean
RestTemplate restTemplate() throws Exception {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
// ... 其他配置
return restTemplate;
}
常見問題
Q1: 憑證已過期怎麼辦?
A: 聯絡 example.text.xxx.com 的管理員更新 SSL 憑證。這是伺服器端的問題,需要由伺服器管理員處理。
Q2: 找不到 JAVA_HOME 環境變數?
A:
# Linux/Mac
export JAVA_HOME=/path/to/java
# Windows (PowerShell)
$env:JAVA_HOME = "C:\Program Files\Java\jdk-11.0.x"
# 或永久設定在系統環境變數中
Q3: keytool 指令找不到?
A: 確保 $JAVA_HOME/bin 已加入 PATH 環境變數:
export PATH=$JAVA_HOME/bin:$PATH
Q4: 匯入憑證後仍然失敗?
A: 1. 確認重新啟動了應用程式 2. 檢查是否匯入了完整的憑證鏈(包含中繼憑證) 3. 確認 Java 應用程式使用的是正確的 JDK 版本 4. 檢查應用程式日誌中的詳細錯誤訊息
檢查清單
執行以下步驟確保問題已解決:
- 執行診斷腳本獲取憑證資訊
- 檢查憑證是否過期
- 將憑證匯入 Java truststore
- 驗證憑證已成功匯入
- 重新啟動應用程式
- 測試 API 呼叫
- 檢查應用程式日誌確認不再有 SSL 錯誤