java - 无法从 Spring 应用程序启动多个 HTTPS 连接

标签 java spring ssl https certificate

我有一个 Spring Boot 应用程序试图打开一个 javax.net.ssl.HttpsURLConnection 到服务器,但收到的响应是:java.io.IOException: Server返回 HTTP 响应代码:403 URL:https://serverIP:8443/path

keyStoretrustStore 和它们的密码 被设置为系统属性 时请求有效正确并收到预期的 JSON 响应:

System.setProperty("javax.net.ssl.keyStore", "src/main/resources/myKeyStore.p12");
System.setProperty("javax.net.ssl.trustStore", "src/main/resources/myTrustStore.truststore");
System.setProperty("javax.net.ssl.keyStorePassword", "myPassword");
System.setProperty("javax.net.ssl.trustStorePassword", "myPassword");

但是当尝试在 SSLContext 中设置信息时收到 403 响应代码,而不是通过使用返回 SSLContext 对象的方法设置系统属性:

public static SSLContext getSslContext(String trustStoreFile, String keystoreFile, String password)
            throws GeneralSecurityException, IOException {
        final KeyStore keystore = KeyStore.getInstance("pkcs12"); // also tried with JKS

        try (final InputStream inKeystore = new FileInputStream(keystoreFile)) {
            keystore.load(inKeystore, password.toCharArray());
        }

        try (final InputStream inTruststore = new FileInputStream(trustStoreFile)) {
            keystore.load(inTruststore, password.toCharArray());
        }

        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX"); // also tried with .getDefaultAlgorithm()
        keyManagerFactory.init(keystore, password.toCharArray());

        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keystore);

        X509TrustManager x509Tm = null;
        for (final TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
            if (trustManager instanceof X509TrustManager) {
                x509Tm = (X509TrustManager) trustManager;
                break;
            }
        }

        final X509TrustManager finalTm = x509Tm;
        final X509ExtendedTrustManager customTm = new X509ExtendedTrustManager() {
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return finalTm.getAcceptedIssuers();
            }

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
            }

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
            }
        };

        final SSLContext sslContext = SSLContext.getInstance("TLS"); // also tried with SSL
        sslContext.init(
                keyManagerFactory.getKeyManagers(),
                new TrustManager[]{customTm},
                new SecureRandom());

        final HostnameVerifier allHostsValid = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        return sslContext;
    }

OBS:trustStore 和 keyStore 具有相同的密码,这就是为什么该方法只有一个密码参数并且用于 key 和信任管理器工厂。

getSslContext方法的调用和使用方式是:

        final SSLContext sslContext = SSLContextHelper.getSslContext("src/main/resources/myTrustStore.truststore",
                                                                     "src/main/resources/myKeyStore.p12", 
                                                                     "myPassword");
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        final URL url = new URL("https://serverIP:8443/path");
        final HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(sslSocketFactory);

        // tried adding some headers to the request
        urlConnection.addRequestProperty("Content-Type", "application/json");
        urlConnection.addRequestProperty("Accept", "application/json");
        urlConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0");
        urlConnection.connect();

        final InputStream inputstream = urlConnection.getInputStream();

尝试获取 URL 连接的 inputStream 时在最后一行抛出错误。

此外,我尝试使用 org.apache.http 中的以下类:SSLConnectionSocketFactory、HttpClient、HttpGet、HttpResponse,但响应代码仍然是 403。

我只能认为 SSL 配置中缺少某些东西,因为系统属性有效。欢迎就我在 SSLContext/SSLSocketFactory 中遗漏的设置或如何解决/更好地调试问题提出任何建议!谢谢!

最佳答案

我设法仅通过使用使用 org.apache. http.client.HttpClient

获取 RestTemplate 的方法是 SSLContext keyStore、trustStore 及其密码以下内容:

public RestTemplate getRestTemplate(final String keyStoreFile, final String trustStoreFile,
                                    final String password) throws Exception {

    final SSLContext sslContext = SSLContextBuilder.create()
                                                   .loadKeyMaterial(ResourceUtils.getFile(keyStoreFile), password.toCharArray(), password.toCharArray())
                                                   .loadTrustMaterial(ResourceUtils.getFile(trustStoreFile), password.toCharArray())
                                                   .build();

    final HttpClient client = HttpClients.custom()
                                         .setSSLContext(sslContext)
                                         .build();

    final HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
    httpComponentsClientHttpRequestFactory.setHttpClient(client);

    return new RestTemplate(httpComponentsClientHttpRequestFactory);
}

RestTemplate 用于 HTTPS 调用的方式是:

final String keyStoreFile = "src/main/resources/myKeyStore.p12";
final String trustStoreFile = "src/main/resources/myTrustStore.truststore";
final String password = "myPassword"; // same password for keyStore and trustStore
final String response = getRestTemplate(keyStoreFile, trustStoreFile, password).getForObject("https://serverIP:8443/path", String.class);
LOGGER.info("Response received: " + response);

希望这对任何人都有帮助,因为 HTTPS 连接有很多困难:)

关于java - 无法从 Spring 应用程序启动多个 HTTPS 连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57372431/

相关文章:

Java:访问 Action 监听器内的变量

TOMCAT SSL 错误 : Alias name does not identify a key entry

ssl - NGINX 中的 gnutls 和 openssl 握手

java - Activity 生命周期 : startActivityForResult and press Back Button

java - 如 Effective Java 中所述,嵌套的 Builder 类真的是必需的吗?

java - AsycTask 抛出 IllegalStateException - fragment 未附加到 Activity

java - 如何使用注释在 @Scope ("prototype") bean 中指定实例特定的 @Value?

java - 如何解决Hibernate中的级联删除问题?

java - OAuth2RestTemplate 将字符集添加到内容类型 header 中

java - Poodle 和 Websphere ESB/Process Server 尝试调用外部 TLS 服务