【问题标题】:Webflux + Webclient + Netty, how to use SslContext with multiple client certificates please?Webflux + Webclient + Netty,如何使用具有多个客户端证书的 SslContext?
【发布时间】:2021-04-07 02:18:08
【问题描述】:

关于 Netty、Spring Webflux 以及如何向多个下游系统发送 http 请求的小问题,当每个下游系统都需要 mTLS 并且需要一个不同的客户端证书来向每个下游系统发送请求时?

到目前为止,我在 Java 11 Spring Webflux 2.4.2 应用程序中用于发送请求的内容是:

@Bean
    @Primary
    public WebClient getWebClient() {
        return WebClient.create().mutate().defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).clientConnector(new ReactorClientHttpConnector(HttpClient.create().wiretap(true).secure(sslContextSpec -> sslContextSpec.sslContext(getSslContext())))).build();
    }

对于 Netty SslContext(顺便说一句,它不是 apache SSLContext)

 public SslContext getSslContext() {
        try {
            final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            try (InputStream file = new FileInputStream(keyStorePath)) {
                final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
                keyStore.load(file, keyStorePassPhrase.toCharArray());
                keyManagerFactory.init(keyStore, keyPassPhrase.toCharArray());
            }

            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            try (InputStream trustStoreFile = new FileInputStream(trustStorePath)) {
                final KeyStore trustStore = KeyStore.getInstance(trustStoreType);
                trustStore.load(trustStoreFile, trustStorePassPhrase.toCharArray());
                trustManagerFactory.init(trustStore);
            }

            return SslContextBuilder.forClient().keyManager(keyManagerFactory).trustManager(trustManagerFactory).build();
        } catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException | UnrecoverableKeyException e) {
            return null;
        }
    }

当我们只需要向下游发送请求时,这甚至可以正常工作。

即使有多个下游,这也可以工作,并且它们接受相同的客户端证书!

但是当每个下游都要求我使用他们各自的客户端证书时,问题就出现了。

请问如何实现?

谢谢

【问题讨论】:

  • 我建议对需要不同证书的不同下游使用不同的 WebClient。

标签: java ssl spring-webflux


【解决方案1】:

选项 1

最直接的解决方案是为每个下游 api 使用特定的客户端。并让每个客户都配置他们特定的客户密钥和信任材料。

选项 2

但您的问题是:如何使用带有多个客户端证书的 SslContext?

所以我想给你一些代码示例来进行工作设置。但简短的回答是:是的,有可能!

长答案是您需要一些额外的配置才能使其正常工作。基本上,您需要做的是从您的 keystore-1 创建一个 keymanagerfactory 并从 keymanagerfactory 获取 keymanager 并为其他两个密钥库重复此操作。之后您将拥有 3 个keymanagers。下一步是拥有一种特殊的密钥管理器,可以提供给 Netty SslContext。这种特殊类型的密钥管理器能够遍历您之前创建的 3 个密钥管理器,它会选择正确的密钥材料与服务器通信。您需要的是CompositeKeyManagerCompositeTrustManager,在此处的以下stackoverflow 答案中提到:Registering multiple keystores in JVM

实际的代码 sn-p 如下。我忽略了使用 inputstream 加载文件并创建密钥库文件和创建 keymanagerfactory,因为您已经知道如何做到这一点。

KeyManager keyManagerOne = keyManagerFactoryOne.getKeyManagers()[0]
KeyManager keyManagerTwo = keyManagerFactoryTwo.getKeyManagers()[0]
KeyManager keyManagerThree = keyManagerFactoryThree.getKeyManagers()[0]

List<KeyManager> keyManagers = new ArrayList<>();
keyManagers.add(keyManagerOne);
keyManagers.add(keyManagerTwo);
keyManagers.add(keyManagerThree);

CompositeX509KeyManager baseKeyManager = new CompositeX509KeyManager(keyManagers);

//repeat the same for the trust material
TrustManager trustManagerOne = trustManagerFactoryOne.getTrustManagers()[0]
TrustManager trustManagerTwo = trustManagerFactoryTwo.getTrustManagers()[0]
TrustManager trustManagerThree = trustManagerFactoryThree.getTrustManagers()[0]

List<TrustManager> trustManagers = new ArrayList<>();
trustManagers.add(trustManagerOne);
trustManagers.add(trustManagerTwo);
trustManagers.add(trustManagerThree);

CompositeX509TrustManager baseTrustManager = new CompositeX509TrustManager(trustManagers);

SslContext sslContext = SslContextBuilder.forClient()
    .keyManager(baseKeyManager)
    .trustManager(baseTrustManager)
    .build();

上面的代码应该让您能够为单个客户端使用多个密钥和信任。该客户端将能够使用不同的密钥和信任材料与不同的下游 api 进行通信。

此设置的缺点是您需要将CompositeKeyManagerCompositeTrustManager 复制并粘贴到您的代码库中,并且设置有点冗长。 Java 没有为此用例提供开箱即用的东西。

选项 3

如果您想要更简单的设置,我建议您使用下面的代码 sn-p:

import io.netty.handler.ssl.SslContext;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.NettySslUtils;

public class App {

    public static void main(String[] args) {
        SSLFactory sslFactory = SSLFactory.builder()
                .withIdentityMaterial(keyStorePathOne, password)
                .withIdentityMaterial(keyStorePathTwo, password)
                .withIdentityMaterial(keyStorePathThree, password)
                .withTrustMaterial(trustStorePathOne, password)
                .withTrustMaterial(trustStorePathTwo, password)
                .withTrustMaterial(trustStorePathThree, password)
                .build();

        SslContext sslContext = NettySslUtils.forClient(sslFactory).build();
    }

}

我需要提供一些免责声明,我是上面代码sn-p的库的维护者。该库可在此处获得:GitHub - SSLContext Kickstart,它使用相同的 CompositeKeyManagerCompositeTrustManager,就像我之前提到的选项 2。

您可以使用以下 sn-p 将其添加到您的 pom:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-netty</artifactId>
    <version>6.6.0</version>
</dependency>

【讨论】:

  • 非常明确的答案。 (upvote)尝试选项
  • 只是询问选项 3,而不是硬编码固定数量的商店(在您的示例中为 3),是否可以放入 for 循环? (我有大约 50 家商店),这也是我想避免选项 1 的原因 :)
  • 是的,您可以将 KeyStore 的列表作为信任材料,并使用 withTrustMaterial(trustStore) 方法将每个带有 for each 循环的密钥库添加到构建器中。身份材料也是如此,但您需要提供密钥密码作为附加参数。列表中的其他选项也是可能的,例如 KeyManager、TrustManager、InputStream 等
  • 我正在尝试选项 3,但不太明白。可能是我的错,我在github.com/Hakky54/sslcontext-kickstart/issues/58 中添加了更多描述。谢谢!
  • 选项 3 是要走的路。这个项目 github.com/Hakky54/sslcontext-kickstart。做得很好
猜你喜欢
  • 2023-02-05
  • 1970-01-01
  • 2019-09-26
  • 1970-01-01
  • 1970-01-01
  • 2015-07-04
  • 2012-04-04
  • 1970-01-01
  • 2021-01-15
相关资源
最近更新 更多