java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java解决SSL证书验证问题

一文解决Java中IP地址访问HTTPS接口的SSL证书验证问题

作者:一勺菠萝丶

这篇文章主要为大家详细介绍了解决Java中IP地址访问HTTPS接口的SSL证书验证问题的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

问题描述

在使用Java的RestTemplate访问HTTPS接口时,如果使用IP地址而不是域名,经常会遇到以下错误:

java.security.cert.CertificateException: No subject alternative names matching IP address 111.63.81.79 found
nested exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names matching IP address 111.63.81.79 found

奇怪的是:使用Postman、curl等工具可以正常访问,但Java代码却报错。

问题原因

SSL证书验证机制

HTTPS协议在建立连接时,会进行SSL证书验证,主要包括两个方面:

为什么Postman可以,Java不行

证书的Subject Alternative Names

SSL证书中有一个字段叫Subject Alternative Names (SAN),它列出了该证书可以用于哪些域名或IP地址。例如:

Subject Alternative Names:
    DNS: example.com
    DNS: www.example.com
    IP Address: 192.168.1.1

如果证书中没有包含你访问的IP地址(如111.63.81.79),Java就会抛出上述异常。

解决方案

方案概述

我们需要配置RestTemplate,让它跳过SSL证书验证。注意:这种方法只适用于开发/测试环境,生产环境应该使用正确的证书。

完整代码实现

创建一个RestTemplateConfig配置类:

package com.example.zbx.config;

import org.apache.http.client.config.RequestConfig;
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.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

/**
 * RestTemplate配置类
 * 配置忽略SSL证书验证,支持使用IP地址访问HTTPS接口
 * @author lxy
 */
@Configuration
public class RestTemplateConfig {

    /**
     * 创建RestTemplate Bean
     * 配置忽略SSL证书验证,支持使用IP地址访问HTTPS接口
     * @return RestTemplate实例
     */
    @Bean
    public RestTemplate restTemplate() {
        try {
            // 创建完全信任所有证书的TrustManager
            TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }

                        @Override
                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                            // 不进行任何检查,信任所有客户端证书
                        }

                        @Override
                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                            // 不进行任何检查,信任所有服务器证书
                        }
                    }
            };

            // 创建SSL上下文,使用自定义的TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

            // 创建SSL连接工厂,忽略主机名验证(包括IP地址验证)
            SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
                    sslContext,
                    NoopHostnameVerifier.INSTANCE
            );

            // 创建请求配置
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(10000)  // 连接超时10秒
                    .setConnectionRequestTimeout(10000)  // 请求超时10秒
                    .setSocketTimeout(30000)  // 读取超时30秒
                    .build();

            // 创建HttpClient
            CloseableHttpClient httpClient = HttpClients.custom()
                    .setSSLSocketFactory(sslSocketFactory)
                    .setDefaultRequestConfig(requestConfig)
                    .evictExpiredConnections()
                    .evictIdleConnections(30, java.util.concurrent.TimeUnit.SECONDS)
                    .build();

            // 创建请求工厂
            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
            factory.setHttpClient(httpClient);
            factory.setConnectTimeout(10000);
            factory.setConnectionRequestTimeout(10000);
            factory.setReadTimeout(30000);

            // 创建RestTemplate
            return new RestTemplate(factory);
        } catch (Exception e) {
            throw new RuntimeException("创建RestTemplate失败", e);
        }
    }
}

关键点说明

1. 自定义TrustManager

TrustManager[] trustAllCerts = new TrustManager[]{
    new X509TrustManager() {
        @Override
        public void checkServerTrusted(X509Certificate[] certs, String authType) {
            // 不进行任何检查,信任所有服务器证书
        }
        // ... 其他方法
    }
};

这个X509TrustManager会跳过所有证书验证,包括:

2. 创建SSL上下文

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

使用自定义的TrustManager初始化SSL上下文。

3. 忽略主机名验证

SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
    sslContext,
    NoopHostnameVerifier.INSTANCE  // 不验证主机名
);

NoopHostnameVerifier.INSTANCE会跳过主机名验证,这样即使证书中没有对应的IP地址也能通过验证。

Maven依赖

确保你的pom.xml中包含以下依赖:

<!-- HttpClient4 (for RestTemplate SSL configuration) -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

使用示例

配置完成后,直接使用RestTemplate即可,无需额外代码:

@Autowired
private RestTemplate restTemplate;

public void callApi() {
    String url = "https://111.63.81.79:10091/api/endpoint";
    ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
    // 处理响应...
}

注意事项

安全警告

生产环境建议

如果必须在生产环境使用,建议:

示例:生产环境的正确做法

// 只信任特定的证书
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream("truststore.jks"), "password".toCharArray());

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(trustStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

常见问题

Q1: 为什么之前可以,现在不行了?

可能的原因:

Q2: 有没有更简单的办法?

对于开发环境,可以设置JVM参数(不推荐):

-Dcom.sun.net.ssl.checkRevocation=false

但这种方法不够灵活,建议使用配置类的方式。

Q3: 会影响其他HTTP请求吗?

不会。这个配置只影响使用RestTemplate的HTTPS请求,HTTP请求不受影响。

Q4: 如何验证配置是否生效?

在代码中添加日志,观察是否还有证书验证错误:

log.info("RestTemplate配置完成,SSL验证已禁用");

如果不再出现CertificateException,说明配置生效了。

总结

到此这篇关于一文解决Java中IP地址访问HTTPS接口的SSL证书验证问题的文章就介绍到这了,更多相关Java解决SSL证书验证问题内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文