简体   繁体   English

如何配置 Reactive WebClient 以使用 2-way TLS?

[英]How to configure a Reactive WebClient to use 2-way TLS?

I'm trying to configure a reactive WebClient to use 2-way TLS.我正在尝试配置一个反应式 WebClient 以使用 2 路 TLS。 I used this answer as a reference.我用这个答案作为参考。 (The one using a WebClientCustomizer, not the one using an InsecureTrustManager). (使用 WebClientCustomizer 的那个,而不是使用 InsecureTrustManager 的那个)。

I double-checked the keystores and truststores on both client and server side, but the server sends back an error saying that the client is not presenting any certificate:我仔细检查了客户端和服务器端的密钥库和信任库,但服务器发回一个错误,指出客户端没有提供任何证书:

  @Bean
  WebClientCustomizer configureWebclient(@Value("${server.ssl.trust-store}") String trustStorePath, @Value("${server.ssl.trust-store-password}") String trustStorePass,
      @Value("${server.ssl.key-store}") String keyStorePath, @Value("${server.ssl.key-store-password}") String keyStorePass, @Value("${server.ssl.key-alias}") String keyAlias) {

    return new WebClientCustomizer() {

      @Override
      public void customize(Builder webClientBuilder) {
        SslContext sslContext;
        try {
          KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
          trustStore.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());

          List<Certificate> certificateCollcetion = Collections.list(trustStore.aliases()).stream().filter(t -> {
            try {
              return trustStore.isCertificateEntry(t);
            } catch (KeyStoreException e1) {
              throw new RuntimeException("Error reading truststore", e1);
            }
          }).map(t -> {
            try {
              return trustStore.getCertificate(t);
            } catch (KeyStoreException e2) {
              throw new RuntimeException("Error reading truststore", e2);
            }
          }).collect(Collectors.toList());

          KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
          keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePass.toCharArray());
          sslContext = SslContextBuilder.forClient()
              .keyManager((PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray()))
              .trustManager((X509Certificate[]) certificateCollcetion.toArray(new X509Certificate[certificateCollcetion.size()]))
              .build();
        } catch (Exception e) {
          log.error("Error creating web client", e);
          throw new RuntimeException(e);
        }
        ClientHttpConnector connector = new ReactorClientHttpConnector((opt) -> {
          opt.sslContext(sslContext);
        });
        webClientBuilder.clientConnector(connector);
      }
    };
  }

Can somebody please share insight on how to correctly configure a reactive WebClient to use 2-way TLS?有人可以分享有关如何正确配置反应式 WebClient 以使用 2-way TLS 的见解吗?

For some reason the server would not accept the client certificate when the ssl context was built like this:出于某种原因,当 ssl 上下文像这样构建时,服务器不会接受客户端证书:

sslContext = SslContextBuilder.forClient()
          .keyManager((PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray()))
          .trustManager((X509Certificate[]) certificateCollcetion.toArray(new X509Certificate[certificateCollcetion.size()]))
          .build();

To fix this, I had to initialize a KeyManagerFactory:为了解决这个问题,我必须初始化一个 KeyManagerFactory:

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, keyStorePass.toCharArray());

Then I initialized the ssl context with the factory:然后我用工厂初始化了 ssl 上下文:

SslContext sslContext = SslContextBuilder.forClient()
                    .keyManager(keyManagerFactory)
                    .trustManager((X509Certificate[]) certificateCollection.toArray(new X509Certificate[certificateCollection.size()]))
                    .build();

After that, the server accepted the certificate and I could connect.之后,服务器接受了证书,我就可以连接了。

In summary, I used this cleaner solution that utilizes factories for both the key-store and the trust-store:总而言之,我使用了这个更简洁的解决方案,该解决方案将工厂用于密钥存储和信任存储:

@Value("${server.ssl.trust-store}")
String trustStorePath;
@Value("${server.ssl.trust-store-password}")
String trustStorePass;
@Value("${server.ssl.key-store}")
String keyStorePath;
@Value("${server.ssl.key-store-password}")
String keyStorePass;

@Bean
public WebClient create2WayTLSWebClient() {

    ClientHttpConnector connector = new ReactorClientHttpConnector(
            options -> {
                options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
                options.sslContext(get2WaySSLContext());
            }
    );

    return WebClient.builder()
            .clientConnector(connector)
            .build();

}

private SslContext get2WaySSLContext() {

    try {

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePass.toCharArray());

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, keyStorePass.toCharArray());

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());

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

        return SslContextBuilder.forClient()
                .keyManager(keyManagerFactory)
                .trustManager(trustManagerFactory)
                .build();

    } catch (Exception e) {
        logger.error("Error creating 2-Way TLS WebClient. Check key-store and trust-store.");
        e.printStackTrace();
    }

    return null;
}

Just a note, if you are using Spring 5.1 or newer, this specific implementation will not work as you can no longer pass HttpClientOptions to a ReactorClientHttpConnector.请注意,如果您使用的是 Spring 5.1 或更高版本,则此特定实现将不起作用,因为您无法再将 HttpClientOptions 传递给 ReactorClientHttpConnector。 Use this link as a guide for that configuration.使用此链接作为该配置的指南。 However the meat of the code in this answer should still be applicable to that sort of configuration.然而,这个答案中的代码内容应该仍然适用于那种配置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Java中的两路tls,在两台服务器之间 - 2-way tls in java, between two servers 如何在Android中将NumberFormat与2向数据绑定一起使用? - How to use NumberFormat with 2-way data binding in android? 如何配置 Spring WebClient 以使用 Gson 而不是 Jackson? - How to configure Spring WebClient to use Gson instead of Jackson? 可以在GAE上使用双向电子邮件中继吗? - Is it possible to use 2-way email relay on GAE? 使用 spring 反应式 webclient 传递 protobuff 请求 - Use spring reactive webclient to pass protobuff request 如何配置Tomcat在出站连接中使用TLS v1.2 - How to configure Tomcat to use TLS v1.2 in OUTBOUND connections 如何配置Spring启动应用程序以通过MySQL使用SSL / TLS? - How to configure spring boot application to use SSL/TLS over MySQL? 如何配置 Reactive Resource Server 以使用带有对称密钥的 JWT? - How to configure a Reactive Resource Server to use a JWT with a symmetric key? 如何从响应式 WebClient 响应中获取数据 - How to get data from reactive WebClient response Spring 反应式 webClient - 如何在 Mono 上调用方法 - Spring reactive webClient - how to call methods on a Mono
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM