Spring boot 允许Spring webclient通过HTTPS与其他启用TLS的服务进行通信

Spring boot 允许Spring webclient通过HTTPS与其他启用TLS的服务进行通信,spring-boot,ssl,spring-webflux,spring-webclient,Spring Boot,Ssl,Spring Webflux,Spring Webclient,我有两个服务A和B,它们应该通过HTTPS相互通信。 我已经使用server.ssl.*属性为这两个应用程序启用了TLS。 我使用WebClient进行通信,服务A将使用WebClient呼叫B,B将向A发送响应。 对于通过TLS进行的通信,我的理解是Webclient需要truststore数据,该数据将具有被称为服务B的服务的证书 import java.io.FileInputStream; import java.io.IOException; import java.nio.file.

我有两个服务A和B,它们应该通过HTTPS相互通信。 我已经使用server.ssl.*属性为这两个应用程序启用了TLS。 我使用WebClient进行通信,服务A将使用WebClient呼叫B,B将向A发送响应。 对于通过TLS进行的通信,我的理解是Webclient需要truststore数据,该数据将具有被称为服务B的服务的证书

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;

import org.springframework.boot.web.server.Ssl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;


import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import reactor.netty.http.client.HttpClient;


@Slf4j
@Configuration
public class WebClientConfig {

    private final Ssl ssl;

    public WebClientConfig(final Ssl ssl) {
        this.ssl = ssl;
    }

    @Bean
    public WebClient createWebClient() throws Exception {

        if (ssl.isEnabled()) {

            return buildSslEnabledWebClient();

        }

        return WebClient.builder().build();
    }

    private WebClient buildSslEnabledWebClient() throws Exception {

        final String trustStorePath = ssl.getTrustStore();
        final String trustStorePassword = ssl.getTrustStorePassword();
        final KeyStore trustStore = createKeyStore(trustStorePath, trustStorePassword);

        try {

            final TrustManagerFactory trustManager = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManager.init(trustStore);

            final SslContext sslContext = SslContextBuilder.forClient().trustManager(trustManager)
                    .build();

            final HttpClient httpClient = HttpClient.create().secure(ssl -> {
                ssl.sslContext(sslContext);
            });

            return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();

        } catch (NoSuchAlgorithmException | KeyStoreException | SSLException e) {
            log.error("Could not initialize Webclient with the trustore data", e);
            throw e;
        }
    }

    private static KeyStore createKeyStore(final String keyStoreLocation, final String keyStorePassword) {

        try (FileInputStream fis = new FileInputStream(keyStoreLocation)) {

            final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

            ks.load(fis, keyStorePassword.toCharArray());

            return ks;

        } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
            throw new IllegalArgumentException(e);
        }
    }
}
我已经使用keytool创建了自签名证书,这似乎起到了作用。我可以从a打电话到B,然后回复

但是,我觉得设置不完整,因此我的问题是:

  • 我错过了什么
  • 这是正确的方法吗
  • 这将是工作在生产与真正的证书

  • 更新:修正了代码,阅读下面的答案后意识到错误。实际上,我正在使用更新的代码。

    在我看来还可以,您正在正确加载密钥库文件,并使用密钥库对象正确初始化trustmanagerfactory

    但是,您没有使用trustmanagerfactory来配置httpclient。相反,您再次从文件加载密钥库,同时将其作为对象传递给方法
    SslContextBuilder.forClient().trustManager
    。但是,这将不起作用,因为在传递密钥库时,此方法需要一个包含PEM格式的受信任证书列表的文件。javadoc包含以下文档:

    public SslContextBuilder trustManager(java.io.File trustCertCollectionFile)
    
    Trusted certificates for verifying the remote endpoint's certificate. The file should contain an X.509 certificate collection in PEM format. null uses the system default.
    
    如果您正在加载一个PEM格式的文件,那么您发布的示例将起作用。如果您正在加载密钥库文件,我建议您使用以下代码段:

    final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trustStore);
    
    final SslContext sslContext = SslContextBuilder.forClient()
        .trustManager(trustManagerFactory)
        .build();
    
    final HttpClient httpClient = HttpClient.create()
        .secure(ssl -> {
            ssl.sslContext(sslContext);
        });
    

    嗨,谢谢你指出错误。我用我实际使用的代码更新了代码,这与您指出的相同。我粘贴的代码来自早期的尝试和错误。是的,这只适用于PEM格式。是否有其他方法使其适用于其他格式?您可以使用已创建的trustmanagerfactory来创建sslcontext。它有多个重载方法,您可以在其中传递不同类型的对象,其中之一是trustmanagerfactory。请看一下第二段代码。它使用trustmanagerfactory对象调用trustmanager方法。对于您的用例来说,这应该可以做到。请告诉我你在哪一点被卡住了