Spring Boot WebFlux WebClient未发送证书

11dmarpk  于 2023-08-04  发布在  Spring
关注(0)|答案(1)|浏览(137)

我正在创建一个基于Spring WebFlux的Java客户端,它应该向外部端点发送请求,通过证书进行认证。我使用Spring的WebServiceTemplate做了类似的事情,在application.yaml文件中指定证书,如下所示:

client:
  ssl:
    enabled: true
    key-store: /myapp/certificate/keystore.p12
    key-store-password: SUPERSECRET

字符串
然后通过为HttpClient定义SSLContext,以便生成用于正确调用的相关bean。下面是上下文配置的摘录:

private SSLContext sslContext()
  throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException,
  KeyManagementException, UnrecoverableKeyException {

  return SSLContextBuilder.create()
    .loadKeyMaterial(new File(keystore), keystorePassword.toCharArray(),
        keystorePassword.toCharArray()).build();
}


事实证明,我不能通过使用WebClient(来自WebFlux)执行完全相同的事情。我所做的,按照各种SO响应和指南,如下所示:
通过加载相同的keystore.p12来创建正确的SSLContext(我故意从下面的代码中删除了try-catch以增加可读性):

private WebClient getMtlsWebClient() {
  HttpClient httpClient = HttpClient.create();

  httpClient.secure(spec -> {
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePass.toCharArray());

    // Set up key manager factory to use key-store
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keyStore, keyStorePass.toCharArray());
    
    spec.sslContext(SslContextBuilder.forClient()
        .keyManager(keyManagerFactory)
        .build());
  });

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


然后简单地像下面这样触发调用:

return getMtlsWebClient()
    .post()
    .uri(externalServiceUrl)
    .contentType(MediaType.TEXT_XML)
    .accept(MediaType.TEXT_XML)
    .acceptCharset(StandardCharsets.UTF_8)
    .body(Mono.just(request), reqClazz)
    .retrieve()
    .bodyToMono(resClazz)
    .retryWhen(
        Retry
            .fixedDelay(3, Duration.ofSeconds(1))
            .filter(this::isAnyError)
    ).
    map(res -> {
      log.trace("Response payload: {}", res);
      return res;
    })
    .onErrorMap(res -> {
      log.error("Error response payload: {}", res);
      return res;
    });


不幸的是,我得到的响应是403禁止,实际上握手日志显示以下消息:

*** ServerHelloDone
[read] MD5 and SHA1 hashes:  len = 4
0000: 0E 00 00 00                                        ....
Warning: no suitable certificate found - continuing without client authentication
*** Certificate chain
<Empty>
***


虽然前面的WebServiceTemplate可以正常工作(我省略了证书链):

*** ServerHelloDone
[read] MD5 and SHA1 hashes:  len = 4
0000: 0E 00 00 00                                        ....
matching alias: myalias
*** Certificate chain


谁能告诉我在上下文配置中缺少什么?
提前感谢您的帮助!
请让我知道如果你需要更多的信息…

zbdgwd5y

zbdgwd5y1#

我知道这是2岁,但这帮助了我:

private SslContext createSSLContext() {
  try {
      KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
      keyStore.load(getClass().getClassLoader().getResourceAsStream(KEYSTORE_FILE), KEYSTORE_PASSWORD.toCharArray());

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

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

  } 
  catch (Exception e) {
      throw new RuntimeException("Error creating SSL context.");
  }
}

private WebClient buildCustomWebClient(WebClient.Builder builder, String baseUrl){

  HttpClient client = HttpClient.create().secure(spec -> spec.sslContext(createSSLContext()));
  ClientHttpConnector connector = new ReactorClientHttpConnector(client);

  return builder
          .baseUrl(baseUrl)
          .clientConnector(connector)
          .build();
}

字符串
来源:https://gist.github.com/hsg/6152944726e46ababcf47398398b4140

相关问题