【问题标题】:Configure HostnameVerifier with reactor netty for spring-webflux WebClient使用 reactor netty 为 spring-webflux WebClient 配置 HostnameVerifier
【发布时间】:2019-01-03 15:22:24
【问题描述】:

我正在尝试使用 ssl 和客户端主机名验证配置 spring-webflux WebClient(在引擎盖下使用反应器 netty)。为我提供了 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();

【问题讨论】:

    标签: java ssl https spring-webflux reactor-netty


    【解决方案1】:

    您应该提供有效的证书颁发机构证书 (trustManager()) 和可选的用户证书以及用于授权的私钥和私钥密码 (keyManager())。 您的服务 SSL 证书应由您在 trustManager() 中定义的同一 CA 签名。

    正在使用服务主机名自动验证主机名。如果不匹配 java.security.cert.CertificateException: No subject alternative names present 将抛出异常。实际上我找不到省略主机名验证的方法(不使用 .trustManager(InsecureTrustManagerFactory.INSTANCE) 省略整个 SSL 证书验证)。

    我已经在本地测试了这个解决方案。我的网络服务在我的本地机器上运行,但它的 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;
      
              }
      

      它使用 nettys handlerConfigurator 来配置 SSLEngine 并将其与自定义匹配器一起使用

      【讨论】:

      • 我发现此答案具有误导性,因为它提到了自定义 SNI(服务器名称指示)的解决方案,而问题似乎与自定义 SAN(主题备用名称)验证更相关(即证书中的服务器名称与真实名称相比)使用的主机名)。
      猜你喜欢
      • 2022-06-05
      • 1970-01-01
      • 2019-05-04
      • 2018-05-09
      • 2019-11-07
      • 2016-12-21
      • 2019-04-04
      • 1970-01-01
      • 2021-12-18
      相关资源
      最近更新 更多