使用Spring RestTemplate通过主机验证来到达HTTPS Rest api

时间:2017-12-15 15:34:59

标签: spring rest ssl ssl-certificate resttemplate

我已经阅读了很多关于这个问题的内容,我认为我找到了最简单的解决方法(最后一个代码示例来自http://www.baeldung.com/httpclient-ssl),但它不起作用。

以下是我如何在关闭主机名验证(以及代理设置)的情况下声明我的RestTemplate:

  @Bean
  public RestTemplate restTemplate(RestTemplateBuilder builder) {

    CloseableHttpClient httpClient = HttpClients.custom()
      .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
      .setProxy(new HttpHost("10.xx.xx.xx", 3128, "http"))
      .build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    return builder.requestFactory(requestFactory).build();

    //return builder.build();
  }

以下是创建POST请求的代码:

  LoginResponse loginResponse = restTemplate.postForObject("https://interflex.svc.suezsmartsolutions.com/path/to/my/api", loginRequest, LoginResponse.class);

这是我得到的例外(就像我没有关闭主机名验证器一样):

16:15:27 ERROR org.springframework.boot.SpringApplication:771 - Application startup failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:735)
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:716)
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:703)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:304)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
    at hello.Application.main(Application.java:23)
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://interflex.svc.suezsmartsolutions.com/path/to/api": sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:673)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:620)
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:387)
    at hello.Application.lambda$0(Application.java:45)
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:732)
    ... 6 common frames omitted
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:961)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
    at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:396)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.upgrade(DefaultHttpClientConnectionOperator.java:193)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.upgrade(PoolingHttpClientConnectionManager.java:375)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:416)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:89)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:659)
    ... 10 common frames omitted
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
    at sun.security.validator.Validator.validate(Validator.java:260)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1496)
    ... 32 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
    ... 38 common frames omitted

有人可以帮我摆脱这个例外吗?

此外,我不知道为什么会出现此异常,因为用于生成站点证书(VeriSign)的根CA存在于我的信任库(cacerts)中(虽然中间权限不存在,但是它可以是原因?)。

3 个答案:

答案 0 :(得分:1)

简单修复,只需跳过证书

TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    new SSLContextBuilder().loadTrustMaterial(null, acceptingTrustStrategy).build();

答案 1 :(得分:0)

您的问题似乎与证书问题有关,而不是代理配置。 无论如何,在我的项目中,我正在使用此配置:

    @Bean
    @Autowired
    public RestTemplate restTemplate(ClientHttpRequestFactory factory)
    {
        RestTemplate result = new RestTemplate(factory);
        return result;
    }

    @Bean
    public ClientHttpRequestFactory requestFactory() throws Exception
    {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(httpClient());
        return factory;
    }

    @Bean
    public HttpClient httpClient() throws Exception
    {
        int timeout = new Integer(env.getProperty("web.http.client.timeout"));
        CloseableHttpClient httpClient = null;
                //I load a JSON where I specify the name and the PWD of keystores I want to use
        String keystores = "keyStoreInfo.json";
        PoolingHttpClientConnectionManager pcm = null;
        if(StringUtils.hasText(keystores))
        {
            Resource jsonRes = new ClassPathResource(keystores);
            if( jsonRes.exists() )
            {
                List<KeyStoreInfo> ksInfo = objectMapper().readValue(jsonRes.getInputStream(), new TypeReference<List<KeyStoreInfo>>()
                {
                });
                SSLContext sslCtx = SSLContext.getInstance("TLS");
                List<KeyManager> keymanagers = new ArrayList<KeyManager>();
                for (KeyStoreInfo ksi : ksInfo)
                {
                    String keystoreName = ksi.getNomeKeyStore();
                    String keyStorePwd = ksi.getPasswordKeyStore();
                    if( StringUtils.hasText(keystoreName) )
                    {
                        Resource keystoreRes = new ClassPathResource(keystoreName);
                        KeyMaterial km = new KeyMaterial(keystoreRes.getInputStream(), keyStorePwd.toCharArray());
                        KeyStore clientStore = km.getKeyStore();
                        KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                        kmfactory.init(clientStore, keyStorePwd != null ? keyStorePwd.toCharArray() : null);
                        keymanagers.addAll(Arrays.asList(kmfactory.getKeyManagers()));
                    }
                }
                if( !keymanagers.isEmpty() )
                {

                    X509TrustManager tm = new X509TrustManager() {

                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
                                throws CertificateException {

                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
                                throws CertificateException {

                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {

                            return null;
                        }

                    };      
                    sslCtx.init(keymanagers.toArray(new KeyManager[keymanagers.size()]), new TrustManager[]{tm}, null);
                    SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(sslCtx);   
                    Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("https", sslConnectionFactory).register("http", new PlainConnectionSocketFactory()).build();
                    pcm = new PoolingHttpClientConnectionManager(registry); 
                }
                else
                {
                    if( logger.isInfoEnabled() )
                    {
                        logger.info("Nessun keystore presente nel JSON di configurazione {}. Creo un PoolingHttpClientConnectionManager di default",keystores);
                    }
                    pcm = new PoolingHttpClientConnectionManager();                 
                }
            }
        }
        else
        {
            if( logger.isInfoEnabled() )
            {
                logger.info("Nessun keystore da caricare. Creo un PoolingHttpClientConnectionManager di default");
            }
            pcm = new PoolingHttpClientConnectionManager();
        }
        HttpClientBuilder hcb = HttpClientBuilder.create();
        pcm.closeIdleConnections(timeout, TimeUnit.MILLISECONDS);
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(timeout).setSocketTimeout(timeout).setConnectTimeout(timeout).build();
        hcb.setDefaultRequestConfig(config);
        hcb.setConnectionManager(pcm).setConnectionManagerShared(true);
        boolean proxyEnable = new Boolean(env.getProperty("web.http.client.proxyEnable"));
        if (proxyEnable)
        {
            int proxyPort = new Integer(env.getProperty("web.http.client.portProxy"));
            String proxyHost = env.getProperty("web.http.client.hostProxy");
            BasicCredentialsProvider credentialProvider = new BasicCredentialsProvider();
            AuthScope scope = new AuthScope(proxyHost, proxyPort);
            String usernameProxy = env.getProperty("web.http.client.usernameProxy");
            String passwordProxy = env.getProperty("web.http.client.passwordProxy");
            if (StringUtils.hasText(usernameProxy) && StringUtils.hasText(passwordProxy))
            {

                UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(usernameProxy, passwordProxy);
                credentialProvider.setCredentials(scope, credentials);
            }
            ProxyRoutePlanner proxyRoutPlanner = new ProxyRoutePlanner(new HttpHost(proxyHost, proxyPort), env.getProperty("web.http.client.urlNotProxy"));
            hcb.setDefaultCredentialsProvider(credentialProvider).setRoutePlanner(proxyRoutPlanner);
        }
        WsKeepAliveStrategy cas = new WsKeepAliveStrategy();
        cas.setTimeout(new Long(timeout));
        hcb.setKeepAliveStrategy(cas);
        httpClient = hcb.build();
        return httpClient;
    }

WsKeepAliveStrategy的位置:

public class WsKeepAliveStrategy implements ConnectionKeepAliveStrategy
{
    private Long timeout;

    @Override
    public long getKeepAliveDuration(HttpResponse response, HttpContext context)
    {
        return timeout;
    }

    public void setTimeout(Long timeout)
    {
        this.timeout = timeout;
    }

}

ProxyRoutePlanner是:

public class ProxyRoutePlanner extends DefaultProxyRoutePlanner
{

    private List<String> urlsNotProxy = null;
    private boolean useAlwaysSuper = false;

    public ProxyRoutePlanner(HttpHost proxy, String urlNotProxy)
    {
        super(proxy);
        if (!StringUtils.hasText(urlNotProxy))
            this.useAlwaysSuper = true;
        else
        {
            this.urlsNotProxy = Arrays.asList(urlNotProxy.split(","));
        }
    }

    @Override
    public HttpRoute determineRoute(HttpHost host, HttpRequest request, HttpContext context) throws HttpException
    {

        String hostname = host.getHostName();
        if (this.useAlwaysSuper || this.urlsNotProxy.contains(hostname) == false)
            return super.determineRoute(host, request, context);// Super method
        // with proxy
        if ("http".equals(host.getSchemeName()))
            return new HttpRoute(host);// Direct Route
        HttpClientContext clientContext = HttpClientContext.adapt(context);
        RequestConfig config = clientContext.getRequestConfig();
        InetAddress local = config.getLocalAddress();
        return new HttpRoute(host, local, true);
    }

}

我正在使用此配置,我没有问题

在任何情况下,您都应该检查在休息调用中使用哪种证书

我希望它有用

安吉洛

答案 2 :(得分:0)

简短的回答是“是的,您需要信任库中的中间权限,而不仅仅是根CA”。