忽略 SSL 證書驗證會導致 中間人攻擊 (MITM) 風險,生產環境必須配置受信任 CA 證書或導入服務器證書到信任庫。
通過實現 X509TrustManager 接口,空實現驗證方法,構建信任所有證書的 SSLContext,替換默認的 Socket 工廠。
package test;
import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.security.*;
import java.security.cert.X509Certificate;
public class HttpsIgnoreCertDemo {
public static void main(String[] args) {
String targetUrl = "https://home.cnblogs.com/u/mjn0"; // 目標 HTTPS 地址
HttpsURLConnection conn = null;
try {
// 1. 創建連接
URL url = new URL(targetUrl);
conn = (HttpsURLConnection) url.openConnection();
// 2. 構建信任所有證書的 SSLContext
SSLContext sslContext = createTrustAllSSLContext();
// 3. 設置自定義 SocketFactory
conn.setSSLSocketFactory(sslContext.getSocketFactory());
// 4. 可選:忽略主機名驗證 (防止 HostnameVerifier 拋異常)
conn.setHostnameVerifier((hostname, session) -> true);
// 5. 發送請求並讀取響應
printResponse(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 創建信任所有證書的 SSLContext
*/
private static SSLContext createTrustAllSSLContext()
throws NoSuchAlgorithmException, KeyManagementException {
// 信任管理器:不驗證證書鏈
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0]; // 返回空數組
}
}
};
// 初始化 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS"); // 或 "SSL"
sslContext.init(null, trustAllCerts, new SecureRandom());
return sslContext;
}
/**
* 讀取並打印響應內容
*/
private static void printResponse(HttpsURLConnection conn) throws IOException {
try (InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader br = new BufferedReader(isr)) {
String line;
StringBuilder response = new StringBuilder();
while ((line = br.readLine()) != null) {
response.append(line).append("\n");
}
System.out.println("響應狀態碼: " + conn.getResponseCode());
System.out.println("響應內容長度: " + response.length());
System.out.println("--- 響應體前 500 字符 ---");
System.out.println(response.substring(0, Math.min(500, response.length())));
}
}
}
SSLContext.getInstance("TLS") // 推薦:TLS 1.2/1.3,現代標準
SSLContext.getInstance("SSL") // 不推薦:SSLv3 已棄用,存在 POODLE 漏洞
即使信任證書,JDK 默認仍會驗證證書 CN/SAN 與請求域名是否匹配。設置:
conn.setHostnameVerifier((hostname, session) -> true); // 忽略主機名驗證
| 方式 | 代碼 | 影響範圍 | 線程安全 |
|---|---|---|---|
| 單連接 | conn.setSSLSocketFactory() | 僅當前連接 | 安全 |
| 全局 | HttpsURLConnection.setDefaultSSLSocketFactory() | JVM 所有 HttpsURLConnection | 不安全 (多線程競態) |
強烈建議使用單連接設置,避免污染全局 SSL 上下文。
// 正規驗證流程 (被我們繞過) 1. 服務器發送證書鏈 (葉子證書 → 中間 CA → 根 CA) 2. 客戶端檢查: - 簽名算法安全性 - 有效期 (NotBefore/NotAfter) - 撤銷狀態 (CRL/OCSP) - 域名匹配 (CN/SAN) - 信任錨點:根 CA 是否在信任庫中 3. 所有通過 → 建立加密通道
# 1. 導出服務器證書 (或從瀏覽器導出 .cer) openssl s_client -connect api.example.com:443 -servername api.example.com server.pem # 2. 導入 JDK cacerts 或自定義信任庫 keytool -importcert -alias api-example -keystore /path/to/truststore.jks -file server.pem -storepass changeit # 3. JVM 啟動參數指定信任庫 -Djavax.net.ssl.trustStore=/path/to/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit
// 加載自定義 TrustStore
KeyStore trustStore = KeyStore.getInstance("JKS");
try (FileInputStream fis = new FileInputStream("/path/to/truststore.jks")) {
trustStore.load(fis, "changeit".toCharArray());
}
// 構建基於該 TrustStore 的 TrustManagerFactory
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// 初始化 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
// 使用
conn.setSSLSocketFactory(sslContext.getSocketFactory());
若服務器要求客戶端證書,需同時設置 KeyManager:
總結
- 開發測試:使用自定義 TrustManager 忽略驗證,代碼簡潔有效
- 生產環境:務必配置受信 CA 證書或導入服務器證書到信任庫
- 最佳實踐:單連接設置 SSLSocketFactory、顯式指定 TLS 協議、避免全局污染
- 雙向認證:需同時配置 KeyManager (客戶端證書) 和 TrustManager (服務器信任)
掌握 HTTPS 底層 SSL/TLS 握手機制,能從根本上解決各類證書異常、連接失敗、安全合規問題。