Apache驼峰SSL连接到宁静的服务

时间:2018-02-02 11:43:14

标签: apache rest ssl apache-camel

我正忙于一个项目,我必须使用特定证书对暴露的休息服务进行GET。我正在使用带有https4组件的apache camel框架。我创建了一个密钥库并使用soapUI对其进行了测试并且连接成功,但是我无法通过我的项目进行连接。

我使用以下页面作为参考:http://camel.apache.org/http4.html

我通过以下配置为HTTP客户端设置了SSL:

 <spring:sslContextParameters id="sslContextParameters">
    <spring:keyManagers keyPassword="xxxx">
        <spring:keyStore resource="classpath:certificates/keystore.jks" password="xxxx"/>
    </spring:keyManagers>
</spring:sslContextParameters>


<setHeader headerName="CamelHttpMethod">
      <simple>GET</simple>
</setHeader>

我的终端配置为:

<to uri="https4://endpointUrl:9007/v1/{id}?sslContextParametersRef=sslContextParameters"/>

我收到的堆栈跟踪:

 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:1904)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:279)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:273)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1446)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:901)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:837)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1023)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1359)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1343)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
at org.apache.camel.component.http4.HttpProducer.executeMethod(HttpProducer.java:301)
at org.apache.camel.component.http4.HttpProducer.process(HttpProducer.java:173)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:145)
at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:163)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:468)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:83)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.component.direct.DirectProducer.process(DirectProducer.java:62)
at org.apache.camel.impl.InterceptSendToEndpoint$1.process(InterceptSendToEndpoint.java:164)
at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:145)
at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:163)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:468)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.processor.ChoiceProcessor.process(ChoiceProcessor.java:117)
at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:163)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:468)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
at org.apache.camel.processor.Pipeline.access$100(Pipeline.java:44)
at org.apache.camel.processor.Pipeline$1.done(Pipeline.java:139)
at org.apache.camel.processor.CamelInternalProcessor$InternalCallback.done(CamelInternalProcessor.java:257)
at org.apache.camel.processor.RedeliveryErrorHandler$1.done(RedeliveryErrorHandler.java:480)
at org.apache.camel.processor.interceptor.TraceInterceptor$1.done(TraceInterceptor.java:180)
at org.apache.camel.processor.SendProcessor$1.done(SendProcessor.java:155)
at org.apache.camel.processor.CamelInternalProcessor$InternalCallback.done(CamelInternalProcessor.java:257)
at org.apache.camel.processor.Pipeline$1.done(Pipeline.java:148)
at org.apache.camel.processor.CamelInternalProcessor$InternalCallback.done(CamelInternalProcessor.java:257)
at org.apache.camel.processor.RedeliveryErrorHandler$1.done(RedeliveryErrorHandler.java:480)
at org.apache.camel.processor.interceptor.TraceInterceptor$1.done(TraceInterceptor.java:180)
at org.apache.camel.processor.SendProcessor$1.done(SendProcessor.java:155)
at org.apache.camel.component.cxf.CxfClientCallback.handleResponse(CxfClientCallback.java:61)
at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:827)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1672)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream$1.run(HTTPConduit.java:1168)
at org.apache.cxf.workqueue.AutomaticWorkQueueImpl$3.run(AutomaticWorkQueueImpl.java:428)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.cxf.workqueue.AutomaticWorkQueueImpl$AWQThreadFactory$1.run(AutomaticWorkQueueImpl.java:353)
at java.lang.Thread.run(Thread.java:745)

非常感谢任何帮助!

1 个答案:

答案 0 :(得分:0)

完全相同:我遵循documented instructions并陷入“ PKIX路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到到请求目标的有效证书路径 ”。有一个快速修复程序,但是如果您要将配置链接到危急的客户端HTTP会话,它将变成一个复杂的设置。

方法1: Doc页面,论坛和this other article会告诉您,设置JVM启动选项“ -Djavax.net.ssl.trustStore = myKeystore.jks -Djavax.net.ssl.trustStorePassword = mystorepass”可以解决此问题,前提是远程方的证书(自己签名或由CA签名,然后带有所有完整的证书链)都在提供的密钥库中作为受信任的证书获取。事实是,HTTP4基于JSSE,并且这些Java启动选项确实在JVM范围内配置堆栈。 另外,您也可以在默认的JVM密钥库jre \ lib \ security \ cacerts(初始密码:“ changeit”)中获取对等方的证书(完整链),因此甚至不需要JVM选项。

如果您有一些外向客户端连接和一些对等证书,这是最简单的方法。

方法2: 在我们的上下文中,有100个以上的远程方,每个远程方平均每两年需要更新一次证书,该方法意味着JVM大约每周都要在更新的密钥库上重新引导一次。我们的高可用性网关不再高可用性。因此,我搜索了一种动态/每连接/编程方式。

下面是CAMEL处理器的简化代码摘录,我们使用它来作为REST或普通HTTP客户端(无论是否带有SSL / TLS,带有或不带有客户端证书(即2路SSL))进行远程连接/ TLS和1路SSL / TLS),并根据对等体的要求结合HTTP基本身份验证。

由于各种原因,在我们的上下文中仍然使用现在较旧的CAMEL版本2.16.3。我尚未测试较新的版本。考虑到Apache CAMEL层下的库,我怀疑没有任何变化。

我在下面的代码中添加了许多注释,详细描述了变体API,以达到相同的效果。因此,您在下面的提示中可以进一步简化代码或尝试使用更新的HTTP4版本进行替代。照原样,该代码可与2.16一起使用,作为Spring应用程序上下文中的CAMEL处理器bean,该Bean在DSL中包含完整的CAMEL路由定义。

在我们的上下文中,我们使用Java代码配置每个会话全动态SSL / TLS出站连接。您可以毫不费力地将我们下面通过java动态设置的部分配置冻结到适合您的上下文的CAMEL XML DSL中。

Maven依赖项处于危险之中

<properties>
    <camel-version>2.16.3</camel-version>
</properties>
...
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-core</artifactId>
    <version>${camel-version}</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-http4</artifactId>
    <version>${camel-version}</version>
    <scope>provided</scope>
</dependency>

从我们的org.apache.camel.Processor中提取的代码(我已删除了许多异常处理并简化了以下代码,以便专注于解决方案):

// relevant imports (partial)
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.component.http4.HttpClientConfigurer;
import org.apache.camel.component.http4.HttpComponent;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;

...
@Override
public void process(Exchange exchange) throws Exception {
    // assume here that we have previously fetched all dynamic connexion parameters in set of java Properties. Of course you can use numerous means to inject connection parameters
    Properties params= ... ;

    // Trick! 'targetURL' is the URI of the http server to call. Its not the same as the Camel endpoint URI (see further "httpUrlToken" placeHolder), on which you configure endpoint options
    // Fact is, we prefer to pass just the target URL as parameter and keep full control on building the CAMEL endpoint URI in java
    String targetURL= params.getProperty("targetURL"); // URL to call, e.g. "http://remoteHost.com/some/servlet/path". Will override the placeholder URL set on the endpoint.

    // default  plain HTTP without SSL/TLS:
    String endPointURI = "http4://httpUrlToken?throwExceptionOnFailure=false"; // with option to prevent exceptions from being thrown for failed response codes. It allows us to process all the response codes in a response Processor

    // Oh yes! we have to manage a map of HttpComponent instances, because the CAMEL doc clearly tells that each instance can only support a single configuration 
    // and our true connector is multithreading where each request may go to a different (dynamic) destination with different SSL settings, 
    // so we actually use a Map of HttpComponent instances of size MAX_THREADS and indexed by the thread ID plus ageing and re-use strategies... but this brings us too far.
    // So, for a single thread per client instance, you can just do:
    HttpComponent httpComponent = exchange.getContext().getComponent("http4", HttpComponent.class);

    // overload in case of SSL/TLS
    if (targetURL.startsWith("https")) { 
        try {
            endPointURI = "https4://httpUrlToken?throwExceptionOnFailure=false"; 
            httpComponent = exchange.getContext().getComponent("https4", HttpComponent.class); // well: "https4" and "http4" are the same, so you may skip this line! (our true HttpComponent map is common to secured and unsecured client connexions)

            // basic SSL context setup as documented elsewhere, should be enough in theory
            SSLContext sslctxt =  getSSLContext(exchange, params.getProperty("keystoreFilePath"), params.getProperty("keystorePassword"), params.getProperty("authenticationMode")); // cfr helper method below
            HttpClientConfigurer httpClientConfig = getEndpointClientConfigurer(sslctxt); // cfr helper method below
            httpComponent.setHttpClientConfigurer(httpClientConfig);
            // from here, if you skip the rest of the configuration, you'll get the exception "sun.security.provider.certpath.SunCertPathBuilderException:unable to find valid certification path to requested target"
            // the SSL context covers certificate validation but not the host name verification process 
            // we de-activate here at the connection factory level (systematically... you may not want that), and link the later to the HTTP component
            HostnameVerifier hnv = new AllowAll();
            SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslctxt, hnv);
            // You may choose to enforce the BasicHttpClientConnectionManager or PoolingHttpClientConnectionManager, cfr CAMEL docs
            // In addition, the following linkage of the connection factory through a Registry that captures the 'https' scheme to your factory is required
            Registry<ConnectionSocketFactory> lookup = RegistryBuilder.<ConnectionSocketFactory>create().register("https", sslSocketFactory).build();
            HttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(lookup);

            // Does not work in 2.16, as documented at http://camel.apache.org/http4.html#HTTP4-UsingtheJSSEConfigurationUtility
            //              ... keystore and key manager setup ...
            //              SSLContextParameters scp = new SSLContextParameters();
            //              scp.setKeyManagers(...);
            //              httpComponent.setSslContextParameters(scp);

            // Not as good as using a connection manager on the HTTP component, although same effects in theory
            //      HttpClientBuilder clientBuilder = HttpClientBuilder.create();
            //      clientBuilder.set... various parameters...
            //      httpClientConfig.configureHttpClient(clientBuilder);

            // Commented-out alternative method to set BasicAuth with user and password
            //  HttpConfiguration httpConfiguration = new HttpConfiguration();
            //  httpConfiguration.setAuthUsername(authUsername);
            //  ... more settings ...
            //  httpComponent.setHttpConfiguration(httpConfiguration);

            // setClientConnectionManager() is compulsory to prevent "SunCertPathBuilderException: unable to find valid certification path to requested target"
            // if instead we bind the connection manager to a clientBuilder, that doesn't work... 
            httpComponent.setClientConnectionManager(connManager);

        } catch (Exception e) { ... ; }
    } 
    // (back to code common to secured and unsecured client sessions)
    // additional parameters on the endpoint as needed, cfr API docs
    httpComponent.set...(...) ;
    // you may want to append these 3 URI options in case of HTTP[S] with Basic Auth 
    if (... basic Auth needed ...) 
        endPointURI += "&authUsername="+params.getProperty("user")+"&authPassword="+params.getProperty("password")+"&authenticationPreemptive=true";

    // *********** ACTUAL TRANSMISSION ********************
    exchange.getIn().setHeader(Exchange.HTTP_URI, targetURL); // needed to overload the "httpUrlToken" placeholder in the endPointURI
    // Next, there are many ways to get a CAMEL Producer or ProducerTemplate
    // e.g. httpComponent.createEndpoint(endPointURI).createProducer()
    // ... in our case we use a template injected from a Spring application context (i.e. <camel:template id="producerTemplate"/>) via constructor arguments on our Processor bean
    try {
       producerTemplate.send(httpComponent.createEndpoint(endPointURI),exchange);
    } catch (Exception e) { ...; }
    // you can then process the HTTP response here, or better dedicate the next 
    // Processor on the CAMEL route to such handlings...
    ...
}

由以上代码调用的支持的辅助方法

private HttpClientConfigurer getEndpointClientConfigurer(final SSLContext sslContext) {
    return new HttpClientConfigurer(){
        @Override
        public void configureHttpClient(HttpClientBuilder clientBuilder) {
          // I put a logger trace here to see if/when the ssl context is actually applied, the outcome was ... weird, try it!
          clientBuilder.setSSLContext(sslContext);
        }
    };
}

/**
 * Build a SSL context with keystore and other parameters according to authentication mode.
 * The keystore may just contain a trusted peer's certificate for 1way cases, and the associated certificate chain up to a trusted root as applicable.
 * The keystore shall too contain one single client private key and certificate for 2way modes. We assume here a same password on keystore and private key.
 * @param authenticationMode one of "1waySSL" "1wayTLS" "2waySSL" "2wayTLS" each possibly suffixed by "noCHECK" as in "1waySSLnoCHECK"
 * @param keystoreFilePath can be null for "noCHECK" modes
 * @param keystorePassword would be null if above is null
 */
private SSLContext getSSLContext(Exchange exchange, String keystoreFilePath, String keystorePassword, String authenticationMode) throws GeneralSecurityException, FileNotFoundException, IOException {
    SSLContext sslContext = SSLContext.getInstance(authenticationMode.substring(4,7).toUpperCase(),"SunJSSE"); 

    //enforce Trust ALL ? pass a trust manager that does not validate certificate chains
    if (authenticationMode.endsWith("noCHECK")) {
        TrustManager[] trustAllCerts = new TrustManager[]{ new TrustALLManager()};
        sslContext.init(null , trustAllCerts, null);
        return sslContext; 
    }

    // we use https, and validate remote cert's by default, henceforth keystore and password become compulsory
    if (null == keystoreFilePath || null == keystorePassword)
        throw new GeneralSecurityException("Config ERROR: using https://... and implicit default AUTHMODE=1waySSL altogether requires to supply keystore parameters");
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    trustStore.load(new FileInputStream(keystoreFilePath), keystorePassword.toCharArray());
    tmf.init(trustStore);
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    if (authenticationMode.charAt(0)=='2') { // our authenticationMode starts with 1way.. or 2way...
        // 2way... case: set the keystore parameters accordingly
        keyStore.load(new FileInputStream(keystoreFilePath), keystorePassword.toCharArray());
        kmf.init(keyStore, keystorePassword.toCharArray());
        sslContext.init(kmf.getKeyManagers() , tmf.getTrustManagers(), new SecureRandom());
    } else {  // 1way... case
        sslContext.init(null , tmf.getTrustManagers(), new SecureRandom());
    }
    return sslContext;
}

// Create a trust manager that does not validate certificate chains
private class TrustALLManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
      return new X509Certificate[0];
    }
}

private static class AllowAll implements HostnameVerifier
{
    @Override
    public boolean verify(String arg0, SSLSession arg1) {
      return true;
    }
}

}

希望这会有所帮助。我花了很多时间试图使其正常工作(尽管我对SSL / TLS原理,安全性,X509等非常了解)...此代码与我对纯净精简Java代码的品味相去甚远。另外,我假设您确实知道如何构建密钥库,提供所有必需的证书链,定义CAMEL路径等。因此,它在Spring Application Context中与Camel 2.16一起使用,并且除了提供提示可以会节省您的时间。