本文介绍了Apache骆驼SSL连接到静态服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am busy with a project where I have to do a GET on an exposed rest service using specific certificates. I am using the apache camel framework with the https4 component. I created a keystore and tested it using soapUI and it connected successfully, but I am however unable to connect through my project.

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

I used the following page as reference: http://camel.apache.org/http4.html

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

I set up the SSL for the HTTP Client through the following configuration:

 <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"/>

我收到的stacktrace:

The stacktrace I am receiving:

 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)

任何帮助将不胜感激!

推荐答案

同样:我遵循了记录的说明,并且过于卡在" PKIX路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到到请求目标的有效认证路径".有一个快速修复程序,但是如果您要将配置链接到危急关头的客户端HTTP会话,它将成为一个复杂的设置.

Just same: I followed documented instructions and got too stuck on "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target". There's a quick fix, but if you want to link the configuration to the client HTTP session at stake, it becomes a complex set-up.

方法1:文档页面,论坛和另一篇文章会告诉您,设置JVM启动选项"-Djavax.net.ssl.trustStore = myKeystore .jks -Djavax.net.ssl.trustStorePassword = mystorepass"确实解决了此问题,但前提是在提供的密钥库.事实是,HTTP4基于JSSE,并且这些java启动选项确实在JVM范围内配置堆栈.另外,您也可以在默认的JVM密钥库jre \ lib \ security \ cacerts(初始密码:"changeit")中获取对等方的证书(完整链),因此甚至不需要JVM选项.

Method 1:Doc pages, forums, and this other article would tell you that setting JVM launch options "-Djavax.net.ssl.trustStore=myKeystore.jks -Djavax.net.ssl.trustStorePassword=mystorepass" do solve the issue, provided the remote parties' certificates (self signed, or signed by a CA but then with all the full certificate chain) were all fetched as Trusted certificates in the supplied keystore. Fact is, HTTP4 is based on JSSE, and these java launch options do configure the stack JVM-wide.As an alternative, you can also fetch peers' certificates (complete chains) in the default JVM keystore jre\lib\security\cacerts (initial password: "changeit") and thus not even need JVM options.

如果您的客户端连接数很少,对等证书也很少,这是最简单的方法.

If you have a few outgoing client connections and few peer certificates, this is the simplest way.

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

Method 2:In our context, with above 100 remote parties, each requiring certificate updates every 2 years in average, that method implies a JVM reboot on an updated keystore about every week. Our highly available gateway is no longer highly available. So I searched a dynamic/per-connexion/programmatic way.

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

Below is a simplified excerpt of code from a CAMEL Processor that we use to remotely connect as REST or plain-vanilla HTTP client, with or without SSL/TLS, and with or without client-side certificate (i.e. 2-way SSL/TLS versus 1-way SSL/TLS), as well as combine HTTP Basic Auth as required by peers.

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

For various reasons the now old CAMEL version 2.16.3 is still used in our context. I have not tested yet newer versions. I suspect no changes given the libraries at stake under the Apache CAMEL layer.

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

I have added in the code below many comments detailling variant API's to the same effect. So you have clues below to further simplify the code or try alternatives with newer HTTP4 versions. As is, the code works with 2.16, as a CAMEL Processor bean within a Spring application context that contains the entire CAMEL route definition in DSL.

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

In our context we use java code for configuring entirely dynamic SSL/TLS outbound connexions per session. You should have no difficulties freezing part of the configuration that we set below dynamically via java, into the CAMEL XML DSL as suitable to your context.

需要依赖的Maven依赖项:

Maven dependencies at stake:

<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中提取的代码(我已删除了许多异常处理并简化了以下代码,以便专注于解决方案):

Code extracted from our org.apache.camel.Processor (I have removed many Exception handling and simplified the code below in order to focus on the solution):

// 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...
    ...
}

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

Supporting helper methods, invoked by above code

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一起使用,并且除了提供提示的其他用途外,没有其他保留.这样可以节省您的时间.

Hope this helps. I spent many hours trying to get it working (although I know well about SSL/TLS principles, security, X509, etc) ... This code is far from my taste for clean and lean java code. In addition I assumed that you do know how to build a keystore, supply all needed certificate chains, define a CAMEL route, etc. As such, it works with Camel 2.16 within a Spring Application Context, and has no other pretention than providing clues that would save you hours.

这篇关于Apache骆驼SSL连接到静态服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-22 23:17