使用反应式Netty为Spring WebFlux WebClient配置HostnameVerifier

19

我正在尝试配置使用 Reactor Netty 的 Spring WebFlux WebClient,以进行 SSL 和客户端主机名验证。我已经获得了 javax.net.ssl.SSLContext、HostnameVerifier 以及可信主机名列表(作为字符串列表)。

目前,我已经使用我的 SSLContext 配置了 WebClient,但是我找不到一种方法来配置主机名验证。

我的问题陈述如下:我有一组受信任的服务主机名(字符串列表)和一个 HostnameVerifier。我想用它来配置我的 WebClient。

在 javax.net.ssl.HostnameVerifier 中是否有可能实现?在 Reactor Netty 中是否有其他替代方案?

这是我到目前为止所做的:

WebClient.builder()
  .clientConnector(
    new ReactorClientHttpConnector(
      opt -> opt.sslContext(new JdkSslContext(mySSLContext, 
                      true, ClientAuth.OPTIONAL))))
  .build();
2个回答

5
您需要提供有效的证书颁发机构证书(trustManager()),并可选择使用带有私钥和私钥密码的用户证书进行授权(keyManager())。您的服务SSL证书应该由与您在trustManager()中定义的相同的CA签名。
主机名会自动与服务主机名进行验证。如果没有匹配,将抛出java.security.cert.CertificateException: No subject alternative names present异常。实际上,我找不到省略主机名验证的方法(除了使用 .trustManager(InsecureTrustManagerFactory.INSTANCE)来省略整个SSL证书验证)。
我已经在本地测试过这个解决方案。我的webservice正在我的本地机器上运行,但它的SSL证书仅包含DNS名称,而不是IP地址。为了我的调试目的,我添加了hosts文件条目,并将我的服务IP映射到正确的DNS名称。
SslContext sslContext = SslContextBuilder
        .forClient()
        .trustManager(new FileInputStream(caPath))
        .keyManager(
                new FileInputStream(userCertPath),
                new FileInputStream(userPrivateKeyPath),
                userPrivateKeyPassword
        )
        .build();

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

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

2
我使用Netty HttpClient尝试了以下解决方案,它也起作用了(通过使用自定义主机名匹配器来禁用主机名验证)。"最初的回答"。
public HttpClient getHttpClient(HttpClientProperties properties){

            // configure pool resources
            HttpClientProperties.Pool pool = properties.getPool();

            ConnectionProvider connectionProvider;
            if (pool.getType() == DISABLED) {
                connectionProvider = ConnectionProvider.newConnection();
            }
            else if (pool.getType() == FIXED) {
                connectionProvider = ConnectionProvider.fixed(pool.getName(),
                        pool.getMaxConnections(), pool.getAcquireTimeout());
            }
            else {
                connectionProvider = ConnectionProvider.elastic(pool.getName());
            }

            HttpClient httpClient = HttpClient.create(connectionProvider)
                    .tcpConfiguration(tcpClient -> {

                        if (properties.getConnectTimeout() != null) {
                            tcpClient = tcpClient.option(
                                    ChannelOption.CONNECT_TIMEOUT_MILLIS,
                                    properties.getConnectTimeout());
                        }

                        // configure proxy if proxy host is set.
                        HttpClientProperties.Proxy proxy = properties.getProxy();

                        if (StringUtils.hasText(proxy.getHost())) {

                            tcpClient = tcpClient.proxy(proxySpec -> {
                                ProxyProvider.Builder builder = proxySpec
                                        .type(ProxyProvider.Proxy.HTTP)
                                        .host(proxy.getHost());

                                PropertyMapper map = PropertyMapper.get();

                                map.from(proxy::getPort).whenNonNull().to(builder::port);
                                map.from(proxy::getUsername).whenHasText()
                                        .to(builder::username);
                                map.from(proxy::getPassword).whenHasText()
                                        .to(password -> builder.password(s -> password));
                                map.from(proxy::getNonProxyHostsPattern).whenHasText()
                                        .to(builder::nonProxyHosts);
                            });
                        }
                        return tcpClient;
                    });

            HttpClientProperties.Ssl ssl = properties.getSsl();
            if (ssl.getTrustedX509CertificatesForTrustManager().length > 0
                    || ssl.isUseInsecureTrustManager()) {
                httpClient = httpClient.secure(sslContextSpec -> {
                    // configure ssl
                    SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();

                    X509Certificate[] trustedX509Certificates = ssl
                            .getTrustedX509CertificatesForTrustManager();
                    if (trustedX509Certificates.length > 0) {
                        sslContextBuilder.trustManager(trustedX509Certificates);
                    }
                    else if (ssl.isUseInsecureTrustManager()) {
                        sslContextBuilder
                                .trustManager(InsecureTrustManagerFactory.INSTANCE);
                    }


                    sslContextSpec.sslContext(sslContextBuilder)
                            .defaultConfiguration(ssl.getDefaultConfigurationType())
                            .handshakeTimeout(ssl.getHandshakeTimeout())
                            .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
                            .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout())
                            .handlerConfigurator(
                                    (handler)->{
                                        SSLEngine engine = handler.engine();
                                        //engine.setNeedClientAuth(true);
                                        SSLParameters params = new SSLParameters();
                                        List<SNIMatcher> matchers = new LinkedList<>();
                                        SNIMatcher matcher = new SNIMatcher(0) {

                                            @Override
                                            public boolean matches(SNIServerName serverName) {
                                                return true;
                                            }
                                        };
                                        matchers.add(matcher);
                                        params.setSNIMatchers(matchers);
                                        engine.setSSLParameters(params);
                                    }
                            )
                    ;
                });
            }

            return httpClient;

        }

它使用Netty的HandlerConfigurator来配置SSLEngine,并与自定义匹配器一起使用。"最初的回答"

3
我认为这个回答有误导性,因为它提到了一个自定义SNI(服务器名称指示)的解决方案,而问题似乎更与自定义SAN(主题备用名称)验证有关(即证书中的服务器名称与实际使用的主机名进行比较)。 - Marcus Wallin

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