Apache Camel Http和SSL

9
我一直在尝试使用Camel实现2-way SSL/HTTPS代理。我已经成功设置了Jetty组件,使用2-way SSL,并且现在正在尝试通过Http4组件来完成代理的客户端部分。
当我将Jetty流量路由到日志组件时,一切正常,2 way SSL信任链也没有问题。但是,当我加入Http4组件时,它会出现对等体未经过身份验证的异常。我正在使用Camel 2.7.0版本。
以下是我目前的进展。
public static void main(String[] args) throws Exception {
    CamelContext context = new DefaultCamelContext();

    JettyHttpComponent jetty = context.getComponent("jetty", JettyHttpComponent.class);

    SslSelectChannelConnector sslConnector = new SslSelectChannelConnector();
    sslConnector.setPort(9443);
    sslConnector.setKeystore("/home/brian/jboss.keystore");
    sslConnector.setKeyPassword("changeit");
    sslConnector.setTruststore("/home/brian/jboss.truststore");
    sslConnector.setTrustPassword("changeit");
    sslConnector.setPassword("changeit");
    sslConnector.setNeedClientAuth(true);

    Map<Integer, SslSelectChannelConnector> connectors = new HashMap<Integer, SslSelectChannelConnector>();
    connectors.put(9443, sslConnector);

    jetty.setSslSocketConnectors(connectors);

    final Endpoint jettyEndpoint = jetty.createEndpoint("jetty:https://localhost:9443/service");

    KeyStore keystore = KeyStore.getInstance("PKCS12");
    keystore.load(new FileInputStream(new File("/home/brian/User2.p12")), "Password1234!".toCharArray());
    X509KeyManager keyManager = new CTSKeyManager(keystore, "user2", "Password1234!".toCharArray());
    KeyManager[] keyManagers = new KeyManager[] { keyManager };

    X509TrustManager trustManager = new EasyTrustManager();
    TrustManager[] trustManagers = new TrustManager[] { trustManager };

    SSLContext sslcontext = SSLContext.getInstance("TLS");
    sslcontext.init(keyManagers, trustManagers, null);

    SchemeRegistry registry = new SchemeRegistry();
    registry.register(new Scheme("https", 443, new SSLSocketFactory(sslcontext,
            SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)));

    HttpComponent http4 = context.getComponent("http4", HttpComponent.class);
    http4.setClientConnectionManager(new ThreadSafeClientConnManager(registry));
    final Endpoint https4Endpoint = http4
            .createEndpoint("https4://soafa-lite-staging:443/axis2/services/SigActService?bridgeEndpoint=true&throwExceptionOnFailure=false");
    context.addRoutes(new RouteBuilder() {

        @Override
        public void configure() {
            from(jettyEndpoint).to(https4Endpoint);
        }
    });

    context.start();

    context.stop();
}

private static class EasyTrustManager 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 null;
    }

};

private static class CTSKeyManager extends X509ExtendedKeyManager {
    private final KeyStore keystore;
    private final char[] privateKeyPassword;
    private final String privateKeyAlias;

    public CTSKeyManager(KeyStore keystore, String privateKeyAlias, char[] privateKeyPassword) {
        this.keystore = keystore;
        this.privateKeyAlias = privateKeyAlias;
        this.privateKeyPassword = privateKeyPassword;
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        String[] serverAliases = null;
        try {
            List<String> aliasList = new ArrayList<String>();
            int count = 0;
            Enumeration<String> aliases = keystore.aliases();
            while (aliases.hasMoreElements()) {
                String alias = aliases.nextElement();
                aliasList.add(alias);
                count++;
            }
            serverAliases = aliasList.toArray(new String[count]);
        } catch (Exception e) {
        }
        return serverAliases;
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        PrivateKey privateKey = null;
        try {
            privateKey = (PrivateKey) keystore.getKey(alias, privateKeyPassword);
        } catch (Exception e) {
        }
        return privateKey;
    }

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return privateKeyAlias == null ? null : new String[] { privateKeyAlias };
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        X509Certificate[] x509 = null;
        try {
            Certificate[] certs = keystore.getCertificateChain(alias);
            if (certs == null || certs.length == 0) {
                return null;
            }
            x509 = new X509Certificate[certs.length];
            for (int i = 0; i < certs.length; i++) {
                x509[i] = (X509Certificate) certs[i];
            }
        } catch (Exception e) {
        }
        return x509;
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        return privateKeyAlias;
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
        return privateKeyAlias;
    }

    @Override
    public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
        return privateKeyAlias;
    }

    @Override
    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
        return privateKeyAlias;
    }
}

}

据我所知,在代理连接的两端使用的所有密钥库/信任库之间应该是没有问题的信任关系。
以下是我的堆栈跟踪。
[ qtp25584663-14] HttpProducer DEBUG 执行http POST方法: https4://soafa-lite-staging:443/axis2/services/SigActService?bridgeEndpoint=true&throwExceptionOnFailure=false [ qtp25584663-14] DefaultErrorHandler DEBUG 交付失败,exchangeId: ID-ubuntu-46528-1303140195358-0-1。第一次尝试交付时捕获到: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated [ qtp25584663-14] DefaultErrorHandler ERROR 交付失败,exchangeId: ID-ubuntu-46528-1303140195358-0-1。尝试交付1次后耗尽,捕获到: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:352) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:390) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121) at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:561) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732) at org.apache.camel.component.http4.HttpProducer.executeMethod(HttpProducer.java:187) at org.apache.camel.component.http4.HttpProducer.process(HttpProducer.java:101) at org.apache.camel.impl.converter.AsyncProcessorTypeConverter$ProcessorToAsyncProcessorBridge.process(AsyncProcessorTypeConverter.java:50) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.SendProcessor$2.doInAsyncProducer(SendProcessor.java:104) at org.apache.camel.impl.ProducerCache.doInAsyncProducer(ProducerCache.java:272) at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:98) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.DelegateAsyncProcessor.processNext(DelegateAsyncProcessor.java:98) at org.apache.camel.processor.DelegateAsyncProcessor.process(DelegateAsyncProcessor.java:89) at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:99) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.RedeliveryErrorHandler.processErrorHandler(RedeliveryErrorHandler.java:299) at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:208) at org.apache.camel.processor.DefaultChannel.process(DefaultChannel.java:269) at org.apache.camel.processor.UnitOfWorkProcessor.process(UnitOfWorkProcessor.java:109) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.DelegateAsyncProcessor.processNext(DelegateAsyncProcessor.java:98) at org.apache.camel.processor.DelegateAsyncProcessor.process(DelegateAsyncProcessor.java:89) at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:68) at org.apache.camel.component.jetty.CamelContinuationServlet.service(CamelContinuationServlet.java:109) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:534) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1351) at org.eclipse.jetty.servlets.MultiPartFilter.doFilter(MultiPartFilter.java:97) at org.apache.camel.component.jetty.CamelMultipartFilter.doFilter(CamelMultipartFilter.java:41) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1322) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:473) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:929) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:403) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:864)
3个回答

15

现在已经可以工作了。事实证明,我对Camel中的端点和协议有一个基本的误解。我应该使用https4协议注册方案,并在其上设置SSLSocketFactory/SSLContext。由于它注册了一个https方案而不是https4,所以Http4组件从未使用过它。

这是我的可行解决方案,但有两个注意点:

  1. 为什么我不能将SchemeRegistry传递给ThreadSafeClientConnManager并在构造HttpClient时使用它?我必须使用HttpClientConfigurer。

  2. Jetty存在一个问题,即必须通过SslSelectChannelConnector上的路径设置Keystore和Truststore,而不是通过SSLContext设置(至少在jetty 7.2.2和7.4.0 -> latest中存在此错误)。

代码:

public class CamelProxy {

/**
 * @param args
 */
public static void main(String[] args) throws Exception {
    CamelContext context = new DefaultCamelContext();

    final Endpoint jettyEndpoint = configureJetty(context);

    final Endpoint https4Endpoint = configureHttpClient(context);

    context.addRoutes(new RouteBuilder() {

        @Override
        public void configure() {
            from(jettyEndpoint).to("log:com.smithforge.request?showAll=true").to(https4Endpoint);
        }
    });

    context.start();

    context.stop();
}

private static Endpoint configureHttpClient(CamelContext context) throws Exception {
    KeyStore keystore = KeyStore.getInstance("PKCS12");
    keystore.load(new FileInputStream(new File("/home/brian/User2.p12")), "Password1234!".toCharArray());

    KeyStore truststore = KeyStore.getInstance("JKS");
    truststore.load(new FileInputStream(new File("/home/brian/jboss.truststore")), "changeit".toCharArray());

    KeyManagerFactory keyFactory = KeyManagerFactory.getInstance("SunX509");
    keyFactory.init(keystore, "Password1234!".toCharArray());

    TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("SunX509");
    trustFactory.init(truststore);

    SSLContext sslcontext = SSLContext.getInstance("TLSv1");
    sslcontext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), null);

    SSLSocketFactory factory = new SSLSocketFactory(sslcontext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    SchemeRegistry registry = new SchemeRegistry();
    final Scheme scheme = new Scheme("https4", 443, factory);
    registry.register(scheme);

    HttpComponent http4 = context.getComponent("http4", HttpComponent.class);
    http4.setHttpClientConfigurer(new HttpClientConfigurer() {

        @Override
        public void configureHttpClient(HttpClient client) {
            client.getConnectionManager().getSchemeRegistry().register(scheme);
        }

    });
    http4.setClientConnectionManager(new ThreadSafeClientConnManager());
    return http4
            .createEndpoint("https4://soafa-lite-staging:443/axis2/services/SigActService?bridgeEndpoint=true&throwExceptionOnFailure=false");
}

private static Endpoint configureJetty(CamelContext context) throws Exception {
    JettyHttpComponent jetty = context.getComponent("jetty", JettyHttpComponent.class);

    SslSelectChannelConnector sslConnector = new SslSelectChannelConnector();
    sslConnector.setPort(4443);
    sslConnector.setKeystore("/home/brian/jboss.keystore");
    sslConnector.setKeyPassword("changeit");
    sslConnector.setTruststore("/home/brian/jboss.truststore");
    sslConnector.setTrustPassword("changeit");
    sslConnector.setPassword("changeit");
    sslConnector.setNeedClientAuth(true);
    sslConnector.setAllowRenegotiate(true);

    Map<Integer, SslSelectChannelConnector> connectors = new HashMap<Integer, SslSelectChannelConnector>();
    connectors.put(4443, sslConnector);

    jetty.setSslSocketConnectors(connectors);
    return jetty.createEndpoint("jetty:https://localhost:4443/service");
}

// .to("log:com.smithforge.response?showHeaders=true");
}

回答注意事项1。Camel总是在其添加到HttpComponent之后将新的https和https4方案注册到ThreadSafeClientConnManager的SchemeRegistry中,这很糟糕,它应该尊重已经存在的方案。这就是为什么需要ClientConfigurer在registerPort()方法完成它的操作后修改SchemeRegistry的原因。 - Brian
1
SchemeRegistry,scheme现在已经被弃用了,请问你能否提供一个没有这些废弃方法的代码库给我? - bhalkian

4
我使用以下代码完成了一个SSL代理的工作。
路线:
public class MyRouteBuilder extends RouteBuilder {

public void configure() {

    configureSslForJetty();
    configureSslForHttp4();

    from("jetty:https://0.0.0.0:4443/topython/?matchOnUriPrefix=true")
    .to("https4://backend.fake.com:4444/?q=ssl&bridgeEndpoint=true&throwExceptionOnFailure=false");
}
...

Jetty的配置(在充当服务器时提供证书)
private void configureSslForJetty()
{
    KeyStoreParameters ksp = new KeyStoreParameters();
    ksp.setResource("c:\\Projects\\blah\\fakefilter.jks");
    ksp.setPassword("123456");

    KeyManagersParameters kmp = new KeyManagersParameters();
    kmp.setKeyStore(ksp);
    kmp.setKeyPassword("export-password");

    SSLContextParameters scp = new SSLContextParameters();
    scp.setKeyManagers(kmp);

    JettyHttpComponent jettyComponent = getContext().getComponent("jetty", JettyHttpComponent.class);
    jettyComponent.setSslContextParameters(scp);
}

https4的配置(作为客户端时我们信任哪些证书签名者)
private void configureSslForHttp4()
{
    KeyStoreParameters trust_ksp = new KeyStoreParameters();
    trust_ksp.setResource("c:\\Projects\\blah\\fakeca.jks");
    trust_ksp.setPassword("123456");

    TrustManagersParameters trustp = new TrustManagersParameters();
    trustp.setKeyStore(trust_ksp);

    SSLContextParameters scp = new SSLContextParameters();
    scp.setTrustManagers(trustp);

    HttpComponent httpComponent = getContext().getComponent("https4", HttpComponent.class);
    httpComponent.setSslContextParameters(scp);
}

值得注意的事项:
  • 您需要配置组件https4而不是http4
  • -Djavax.net.debug=ssl在命令行中提供了大量有用的日志记录

0

看起来你在处理 '不安全的SSL重新协商' 时遇到了问题。请确认你正在使用最新的JDK/JRE(至少1.6.0_24)。


我正在使用最新的JRE/JDK版本。 - Brian
客户端设置发送证书了吗?您能否注释掉sslConnector.setNeedClientAuth(true)这一行并查看是否一切正常? - hooknc
我有一个部分答案,仍在调试中,但看起来apache camel并没有使用提供给Http4组件的SSLContext。我已经调试到了org.apache.http.conn.ssl.SSLSocketFactory,并且可以看到KeyManager为空,TrustManager包含Java cacerts中的证书,而不是我提供的truststore与SSLContext。没有使用Camel,我已经使用了一个独立的嵌入式Jetty和HttpClient进行了clientAuth,所以当它为我配置这些组件时,我知道这是Camel做的一些坏事情。 - Brian
看起来Apache Camel没有使用提供给Http4组件的SSLContext。我认为你必须获取组件**https4**,即context.getComponent("https4", HttpComponent.class);,而不是http4(就像我浪费了几个小时一样...) - xverges

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接