簡體   English   中英

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

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

我正在嘗試配置一個反應式 WebClient 以使用 2 路 TLS。 我用這個答案作為參考。 (使用 WebClientCustomizer 的那個,而不是使用 InsecureTrustManager 的那個)。

我仔細檢查了客戶端和服務器端的密鑰庫和信任庫,但服務器發回一個錯誤,指出客戶端沒有提供任何證書:

  @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);
      }
    };
  }

有人可以分享有關如何正確配置反應式 WebClient 以使用 2-way TLS 的見解嗎?

出於某種原因,當 ssl 上下文像這樣構建時,服務器不會接受客戶端證書:

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

為了解決這個問題,我必須初始化一個 KeyManagerFactory:

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

然后我用工廠初始化了 ssl 上下文:

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

之后,服務器接受了證書,我就可以連接了。

總而言之,我使用了這個更簡潔的解決方案,該解決方案將工廠用於密鑰存儲和信任存儲:

@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;
}

請注意,如果您使用的是 Spring 5.1 或更高版本,則此特定實現將不起作用,因為您無法再將 HttpClientOptions 傳遞給 ReactorClientHttpConnector。 使用此鏈接作為該配置的指南。 然而,這個答案中的代碼內容應該仍然適用於那種配置。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM