錯誤信息
resttemplate I/O error on GET request for “https://21.24.6.6:9443/authn-api/v5/oauth/token”: java.security.cert.CertificateException: No subject alternative names present; nested exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present
這個錯誤是由于 SSL/TLS 握手失敗引起的,具體原因是服務器的 SSL 證書無效或不受信任。錯誤信息 No subject alternative names present 表示服務器的證書中沒有包含與請求的域名匹配的主題備用名稱(Subject Alternative Name, SAN)。
以下是解決此問題的幾種方法:
1. 檢查服務器證書
確保服務器的 SSL 證書是有效的,并且包含正確的主機名(即請求的域名)。可以通過以下步驟檢查證書:
-
使用瀏覽器訪問服務器的 URL(例如 https://21.24.6.6:9443),查看證書的詳細信息。
-
確保證書的 Subject Alternative Name (SAN) 包含請求的域名或 IP 地址。
如果證書無效或不匹配,需要聯系服務器管理員更新證書。
2. 忽略 SSL 證書驗證(僅用于測試環境)
在生產環境中不建議使用此方法,但在測試環境中可以臨時忽略 SSL 證書驗證。
使用 RestTemplate 忽略 SSL 驗證
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;public class RestTemplateExample {public static void main(String[] args) throws Exception {// 創建忽略 SSL 驗證的 RestTemplateRestTemplate restTemplate = createRestTemplateWithIgnoreSSL();// 發送請求String url = "https://21.24.6.6:9443/authn-api/v5/oauth/token";String response = restTemplate.getForObject(url, String.class);System.out.println("Response: " + response);}private static RestTemplate createRestTemplateWithIgnoreSSL() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {// 創建 SSLContext,忽略證書驗證SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (certificate, authType) -> true) // 信任所有證書.build();// 創建 HttpClientCloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) // 忽略主機名驗證.build();// 創建 RestTemplateHttpComponentsClientHttpRequestFactory requestFactory =new HttpComponentsClientHttpRequestFactory(httpClient);return new RestTemplate(requestFactory);}
}
3. 使用自定義信任庫
如果服務器使用的是自簽名證書或私有 CA 簽發的證書,可以將證書導入 Java 的信任庫(cacerts)或自定義信任庫。
步驟:
-
導出服務器的證書:
-
使用瀏覽器訪問服務器的 URL,導出證書(通常為 .crt 或 .pem 格式)。
-
或者使用 openssl 命令導出證書:
-
openssl s_client -connect 21.24.6.6:9443 -showcerts </dev/null 2>/dev/null | openssl x509 -outform PEM > server.crt
- 將證書導入 Java 信任庫:
-
找到 Java 的默認信任庫(通常位于
$JAVA_HOME/lib/security/cacerts
)。 -
使用 keytool 將證書導入信任庫:
-
keytool -import -trustcacerts -file server.crt -keystore $JAVA_HOME/lib/security/cacerts -alias server_cert
- 默認密碼是 changeit。
- 配置 RestTemplate 使用自定義信任庫:
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyStore;public class RestTemplateExample {public static void main(String[] args) throws Exception {// 創建使用自定義信任庫的 RestTemplateRestTemplate restTemplate = createRestTemplateWithCustomTrustStore();// 發送請求String url = "https://21.24.6.6:9443/authn-api/v5/oauth/token";String response = restTemplate.getForObject(url, String.class);System.out.println("Response: " + response);}private static RestTemplate createRestTemplateWithCustomTrustStore() throws Exception {// 加載自定義信任庫KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());try (FileInputStream fis = new FileInputStream("path/to/truststore.jks")) {trustStore.load(fis, "password".toCharArray());}// 創建 SSLContextSSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(trustStore, null) // 使用自定義信任庫.build();// 創建 HttpClientCloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();// 創建 RestTemplateHttpComponentsClientHttpRequestFactory requestFactory =new HttpComponentsClientHttpRequestFactory(httpClient);return new RestTemplate(requestFactory);}
}
4. 檢查服務器配置
如果服務器配置了 SNI(Server Name Indication),確保客戶端支持 SNI。某些舊版本的 Java 可能不支持 SNI,可以嘗試升級 Java 版本。
5. 使用 WebClient 替代 RestTemplate
如果使用的是 Spring WebFlux,可以考慮使用 WebClient,它提供了更靈活的 SSL 配置選項。
示例:
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;public class WebClientExample {public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException {// 創建忽略 SSL 驗證的 WebClientWebClient webClient = WebClient.builder().baseUrl("https://21.24.6.6:9443").build();// 發送請求Mono<String> response = webClient.get().uri("/authn-api/v5/oauth/token").retrieve().bodyToMono(String.class);response.subscribe(body -> System.out.println("Response: " + body));}
}
總結
- 如果服務器證書無效或不匹配,需要更新證書。
- 在測試環境中可以臨時忽略 SSL 驗證,但不建議在生產環境中使用。
- 對于自簽名證書或私有 CA 簽發的證書,可以將證書導入 Java 信任庫或自定義信任庫。
- 如果問題仍然存在,可以嘗試使用 WebClient 替代 RestTemplate。