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(推薦)

  1. 獲取憑證(使用上述工具)

  2. 匯入憑證到 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

  1. 驗證憑證已匯入:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit | grep example.text.xxx.com
  1. 重新啟動應用程式

方案 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 錯誤

參考資料