WebFlux WebClient 未发送证书

问题描述 投票:0回答:2

我正在创建一个基于 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 FORBIDDEN,实际上握手日志显示以下消息:

*** 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

任何人都可以建议我在上下文配置中缺少什么吗?

提前感谢您的帮助!

如果您需要更多信息,请告诉我...

java spring-boot ssl spring-webflux client-certificates
2个回答
1
投票

我知道它已经 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


0
投票

因为调用“secure()”方法时返回的是新的HttpClient对象,但在“ReactorClientHttpConnector()”方法中设置了之前的HttpClient对象。

public final HttpClient secure(Consumer<? super SslProvider.SslContextSpec> sslProviderBuilder) {
        Objects.requireNonNull(sslProviderBuilder, "sslProviderBuilder");
        SslProvider.SslContextSpec builder = SslProvider.builder();
        sslProviderBuilder.accept(builder);
        SslProvider sslProvider = HttpClientSecure.sslProvider(((SslProvider.Builder) builder).build());
        if (sslProvider.equals(configuration().sslProvider)) {
            return this;
        }
        HttpClient dup = duplicate();
        dup.configuration().sslProvider = sslProvider;
        return dup;
    }


@Override
    protected HttpClient duplicate() {
        return new HttpClientConnect(new HttpClientConfig(config));
    }
© www.soinside.com 2019 - 2024. All rights reserved.