错误信息
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。