如何将客户端证书添加到Spring WebClient?

15

我正在构建一个Spring WebClient,它会内部调用托管在不同服务器上的REST API。为此,我需要在每个握手请求中发送公钥(.cert)和私钥(.key)。

我不确定如何使用Spring WebClient完成这项任务。

我尝试设置WebClient,但遇到了添加此代码段的难题:

WebClient Builder

this.webCLient = WebClient.builder()
                .baseUrl("https://some-rest-api.com")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
                .build();

实际调用

this.webClient.get()
                .uri("/getData")
                .exchange()
                .flatMap(clientResponse -> {
                    System.out.println(clientResponse);
                    return clientResponse.bodyToMono(MyClass.class);
                });

由于请求中没有添加证书,我在日志中收到了握手错误。

javax.net.ssl.SSLException: Received fatal alert: handshake_failure

如何将这些证书添加到WebClient请求中,以便我不会出现此错误?我有这些证书,但不确定如何添加它们。

3个回答

14

我花了一些时间才找到Thomas回答中缺失的部分。

在这里:


public static SslContext getTwoWaySslContext() {
        
    try(FileInputStream keyStoreFileInputStream = new FileInputStream(ResourceUtils.getFile(clientSslKeyStoreClassPath));
        FileInputStream trustStoreFileInputStream = new FileInputStream(ResourceUtils.getFile(clientSslTrustStoreClassPath));   
        ) {
        KeyStore keyStore = KeyStore.getInstance("jks");
        keyStore.load(keyStoreFileInputStream, clientSslKeyStorePassword.toCharArray());
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, clientSslKeyStorePassword.toCharArray());
            
        KeyStore trustStore = KeyStore.getInstance("jks");
        trustStore.load(trustStoreFileInputStream, clientSslTrustStorePassword.toCharArray());
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
        trustManagerFactory.init(trustStore);
            
        return SslContextBuilder.forClient()
            .keyManager(keyManagerFactory)
            .trustManager(trustManagerFactory)
            .build();
            
    } catch (Exception e) {
        log.error("An error has occurred: ", e);
    }
        
    return null;
}


HttpClient httpClient = HttpClient.create().secure(sslSpec -> sslSpec.sslContext(SslUtil.getTwoWaySslContext()));
ClientHttpConnector clientHttpConnector = new ReactorClientHttpConnector(httpClient);
WebClient webClient = webClientBuilder
    .clientConnector(clientHttpConnector)
    .baseUrl(baseUrl)
    .build();

享受!


4
从文档中获取:Spring Webclient - Reactor Netty
要访问SSL配置,您需要提供一个带有自定义sslContext的自定义netty HttpClient。
SslContext sslContext = SslContextBuilder
        .forClient()
        // build your ssl context here
        .build();

HttpClient httpClient = HttpClient.create().secure(sslSpec -> sslSpec.sslContext(sslContext));

WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build();

2
1. 首先,您需要使用所需的密钥创建密钥库。
您可以使用Java的keytool或更用户友好的keystore-explorer
2. 接下来,您只需要将其加载到HttpClient中。 Apache HttpComponents提供了更方便的创建SSLContext和配置mTLS的方式:
使用 - Apache HttpClient » 5.2.1
@Configuration
public class WebClientConfigConfiguration {

 @Value("${keystore.uri}")
 private Resource keystore;

 @Value("${keystore.password:}")
 private String password;

 @Bean
 public WebClient webClient() throws GeneralSecurityException, IOException {
   SSLContext sslContext = new SSLContextBuilder()
       .loadKeyMaterial(keystore.getURL(), password.toCharArray(), password.toCharArray())
       .build();

   Registry<TlsStrategy> registry = RegistryBuilder.<TlsStrategy>create()
       .register(URIScheme.HTTPS.getId(), new BasicClientTlsStrategy(sslContext))
       .build();

   CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
       .setConnectionManager(new PoolingAsyncClientConnectionManager(registry))
       .build();

   return WebClient.builder()
       .clientConnector(new HttpComponentsClientHttpConnector(httpClient))
       .build();
 }
}


你甚至可以更进一步,用Java提供的那个替换HttpClient
使用 - Spring Web » 6.0.13
...
  @Bean
  public WebClient webClient() throws GeneralSecurityException, IOException {
    SSLContext sslContext = new SSLContextBuilder()
        .loadKeyMaterial(keystore.getURL(), password.toCharArray(), password.toCharArray())
        .build();

    HttpClient httpClient = HttpClient.newBuilder()
        .sslContext(sslContext)
        .build();

    return WebClient.builder()
        .clientConnector(new JdkClientHttpConnector(httpClient))
        .build();
  }
...

这个页面可能在比较这两个选项时会很有用: https://www.wiremock.io/post/java-http-client-comparison

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接