【问题标题】:How to disable hostname verification in spring webclient?如何在spring webclient中禁用主机名验证?
【发布时间】:2019-10-04 16:09:03
【问题描述】:

我正在使用spring webflux webclient工具调用API。 API服务器地址是HTTPS,是一个没有域名的IP地址。我需要在 webclient 中禁用主机名验证。现在异常如下

Caused by: java.security.cert.CertificateException: No subject alternative names matching IP address 180.101.147.89 found
    at sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:168) ~[na:1.8.0_211]
    at sun.security.util.HostnameChecker.match(HostnameChecker.java:94) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:461) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:442) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:260) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) ~[na:1.8.0_211]
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1626) ~[na:1.8.0_211]
    ... 28 common frames omitted
@Bean
    public WebClient telcomWebclient(WebClient.Builder webClientBuilder,
                                     @Value("${telcom.api.host}") String telcomApiHost,
                                     @Value("${telcom.api.certificate-name}") String telcomApiCertificateName,
                                     @Value("${telcom.api.certificate-store-pass}") String telcomApiCertificateStorePass) {
        try {
            KeyStore selfCert = KeyStore.getInstance("pkcs12");
            selfCert.load(getClass().getResourceAsStream("/cert/outgoing.CertwithKey.pkcs12"), "IoM@1234".toCharArray());
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
            kmf.init(selfCert, "IoM@1234".toCharArray());

            KeyStore caCert = KeyStore.getInstance("jks");
            caCert.load(getClass().getResourceAsStream("/cert/" + telcomApiCertificateName), telcomApiCertificateStorePass.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
            tmf.init(caCert);

            SslContext sslContext = SslContextBuilder.forClient()
                    .keyManager(kmf)
                    .trustManager(tmf)
                    .build();
            HttpClient httpClient = HttpClient.create().create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
            ClientHttpConnector clientHttpConnector = new ReactorClientHttpConnector(httpClient);
            return webClientBuilder.clientConnector(clientHttpConnector).baseUrl(telcomApiHost).build();
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException e) {
            log.error("Config webclient,error occurs", e);
            System.exit(-1);
        }
        return null;
    }

【问题讨论】:

    标签: java


    【解决方案1】:

    除了完全禁用 SSL 验证之外,(我不推荐)像这样传入InsecureTrustManagerFactory.INSTANCE

    SslContext sslContext = SslContextBuilder.forClient()
                        .keyManager(kmf)
                        .trustManager(InsecureTrustManagerFactory.INSTANCE)
                        .build();
    

    您可以通过配置自定义SNIMatcher 来配置 HttpClient 实质上覆盖主机名验证,如下所示:

    HttpClient.create().create().secure(sslContextSpec -> sslContextSpec
        .sslContext(sslContext)
        .handlerConfigurator(sslHandler -> 
            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);
    );
    

    我已经对此进行了测试并确认它有效。我希望这会有所帮助!

    这是受到此处答案的启发:Configure HostnameVerifier with reactor netty for spring-webflux WebClient

    【讨论】:

    • 太好了!非常感谢!
    • @James 它工作正常,并绕过 webclient 中的所有主机名验证。如果我们只想为少数主机绕过主机名验证并且其余的仍应验证,我们有什么解决方案吗?类似于 X509Hostnameverifier 在调用 rest 模板时我们可以添加检查我们想要绕过的域。
    【解决方案2】:

    实际上 SNIMatcher 仅适用于服务器端。这可以通过放置来验证 match() 方法返回的断点。执行永远不会在那个断点处停止。
    跳过主机名验证的实际原因是,当 SSLParameters 被覆盖时,新的 params.identificationAlgorithm 值为 null 而不是“HTTPS”。
    这是一个更简单的代码,它的工作原理和上面一样。

               HttpClient.create().secure(sslContextSpec -> sslContextSpec
                       .sslContext(sslContext)
                       .handlerConfigurator(sslHandler -> {
                           SSLEngine engine = handler.engine();
                           SSLParameters params = new SSLParameters(engine.getSSLParameters().getCipherSuites(), engine.getSSLParameters().getProtocols());
                           // With java update null value is no longer sufficient to skip host name verification
                           params.setEndpointIdentificationAlgorithm("");
                           engine.setSSLParameters(params);
                       }));
    

    【讨论】:

      猜你喜欢
      • 2017-04-25
      • 1970-01-01
      • 1970-01-01
      • 2014-03-19
      • 1970-01-01
      • 2020-05-17
      • 1970-01-01
      • 2020-07-10
      • 2011-05-27
      相关资源
      最近更新 更多