Java Apache HttpClient 4.3 和 x509 客户端证书进行身份验证

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22606579/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:49:09  来源:igfitidea点击:

Apache HttpClient 4.3 and x509 client certificate to authenticate

javasslhttpclientx509certificatespring-ws

提问by Tomas Hanus

now I looking for solution regarding task how to rewrite deprecated solution for client side x509 certificate authentication via HttpComponentsMessageSender (not relevant).

现在我正在寻找有关如何通过 HttpComponentsMessageSender(不相关)重写客户端 x509 证书身份验证的已弃用解决方案的任务的解决方案。

For example, deprecated solution is:

例如,不推荐使用的解决方案是:

    SSLSocketFactory lSchemeSocketFactory = new SSLSocketFactory(this.keyStore, this.keyStorePassword);
    Scheme sch = new Scheme("https", 443, lSchemeSocketFactory);

    DefaultHttpClient httpClient = (DefaultHttpClient)getHttpClient();
    httpClient.getConnectionManager().getSchemeRegistry().register(sch);

As new solution with CloseableHttpClient I am using:

作为 CloseableHttpClient 的新解决方案,我正在使用:

    SSLContextBuilder sslContextBuilder = SSLContexts.custom()
            // this key store must contain the key/cert of the client
            .loadKeyMaterial(keyStore, keyStorePassword.toCharArray());

    if (trustStore != null) {
        // this key store must contain the certs needed and trusted to verify the servers cert
        sslContextBuilder.loadTrustMaterial(trustStore);
    }

    SSLContext sslContext = sslContextBuilder.build();

    LayeredConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);

    // Create a registry of custom connection socket factories for supported
    // protocol schemes / https
    Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("https", sslsf)
            .register("http", new PlainConnectionSocketFactory())
            .build();

    PoolingHttpClientConnectionManager connPoolControl =
            new PoolingHttpClientConnectionManager(socketFactoryRegistry);
    setConnPoolControl(connPoolControl);
    getClientBuilder().setSSLSocketFactory(sslsf);

I still get 403 forbidden from server. But when I use "deprecated" version of the solution, it works great. SSL certificate is signed Thawte.

我仍然从服务器收到 403 禁止。但是当我使用“弃用”版本的解决方案时,它工作得很好。SSL 证书由 Thawte 签名。

Any idea? Thanks

任何的想法?谢谢

回答by Daniyar

Tomas, maybe it's too late, but I hope it will help others... There is the method, which I'm using to create CloseableHttpClient using Apache HttpClient 4.3:

托马斯,也许为时已晚,但我希望它能帮助其他人......有一种方法,我正在使用它来使用 Apache HttpClient 4.3 创建 CloseableHttpClient:

public static CloseableHttpClient prepareClient() {
    try {           
        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build();
        HttpClientBuilder builder = HttpClientBuilder.create();
        SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        builder.setSSLSocketFactory(sslConnectionFactory);
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", sslConnectionFactory)
                .register("http", new PlainConnectionSocketFactory())
                .build();
        HttpClientConnectionManager ccm = new BasicHttpClientConnectionManager(registry);
        builder.setConnectionManager(ccm);
        return builder.build();
    } catch (Exception ex) {

        return null;
    }
}

Apache Foundation moved org.apache.http.conn.ssl.SSLContextBuilder, org.apache.http.conn.ssl.SSLContexts and org.apache.http.conn.ssl.SSLSocketFactory to deprecated starting with 4.4 version, Thereyou can find Apache Client 4.5.2 API Depracated List. So, pervious method can be changed like this:

Apache Foundation 将 org.apache.http.conn.ssl.SSLContextBuilder、org.apache.http.conn.ssl.SSLContexts 和 org.apache.http.conn.ssl.SSLSocketFactory 从 4.4 版本开始弃用,在那里你可以找到 Apache客户端 4.5.2 API 弃用列表。因此,可以像这样更改以前的方法:

public static CloseableHttpClient prepareClient() {
    try {
        SSLContext sslContext = SSLContexts.custom()
                .loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
        HttpClientBuilder builder = HttpClientBuilder.create();
        SSLConnectionSocketFactory sslConnectionFactory = 
                new SSLConnectionSocketFactory(sslContext.getSocketFactory(), 
                        new NoopHostnameVerifier());
        builder.setSSLSocketFactory(sslConnectionFactory);
        Registry<ConnectionSocketFactory> registry = 
                RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", sslConnectionFactory)
                .register("http", new PlainConnectionSocketFactory())
                .build();
        HttpClientConnectionManager ccm = new BasicHttpClientConnectionManager(registry);
        builder.setConnectionManager(ccm);
        return builder.build();
    } catch (Exception ex) {
        LOG.error("couldn't create httpClient!! {}", ex.getMessage(), ex);
        return null;
    }
}

NoopHostnameVerifier

NoopH​​ostnameVerifier

The NO_OP HostnameVerifier essentially turns hostname verification off. This implementation is a no-op, and never throws the SSLException.

NO_OP HostnameVerifier 实质上关闭了主机名验证。此实现是无操作的,并且从不抛出 SSLException。

If you need to verify hostname, you can use DefaultHostnameVerifier or you can implement your custom hostname verifier.

如果您需要验证主机名,您可以使用 DefaultHostnameVerifier 或者您可以实现您的自定义主机名验证程序。

回答by manikanta

Below is the code for HttpClient 4.4+ (updated @Daniyar code for 4.4+)

下面是 HttpClient 4.4+ 的代码(更新了 4.4+ 的 @Daniyar 代码)

import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;

public static CloseableHttpClient createApacheHttp4ClientWithClientCertAuth() {
    try {
        SSLContext sslContext = SSLContexts
                .custom()
                .loadTrustMaterial(null, new TrustSelfSignedStrategy())
                .build();

        SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(sslContext,
                new DefaultHostnameVerifier());

        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("https", sslConnectionFactory)
                .register("http", new PlainConnectionSocketFactory())
                .build();

        HttpClientBuilder builder = HttpClientBuilder.create();
        builder.setSSLSocketFactory(sslConnectionFactory);
        builder.setConnectionManager(new PoolingHttpClientConnectionManager(registry));

        return builder.build();
    } catch (Exception ex) {

        return null;
    }
}

回答by lmiguelmh

You need to create a keystore that containts the trusted CAs i.e. trust.jks. In this keystore you should put only the certificate of the server that your application is going to connect.

您需要创建一个包含受信任 CA 的密钥库,即trust.jks. 在这个密钥库中,您应该只放置您的应用程序将要连接的服务器的证书。

Then, you need a keystore for the identity of the server i.e. identity.jks. In this keystore you should store put the private key + certificate + CA chain under an alias (a name) that your application is going to use to authenticate itself with the server.

然后,您需要一个用于服务器身份的密钥库,即identity.jks. 在此密钥库中,您应该将私钥 + 证书 + CA 链存储在一个别名(名称)下,您的应用程序将使用该别名(名称)来向服务器进行身份验证。

Then you could build the HttpClientlike this:

然后你可以HttpClient像这样构建:

public static HttpClient getHttpClient() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, KeyManagementException {

    KeyStore identityKeyStore = KeyStore.getInstance("jks");
    identityKeyStore.load(MyClass.class.getClassLoader().getResourceAsStream("identity.jks"), "identity_password".toCharArray());

    KeyStore trustKeyStore = KeyStore.getInstance("jks");
    trustKeyStore.load(MyClass.class.getClassLoader().getResourceAsStream("trust.jks"), "trust_password".toCharArray());

    SSLContext sslContext = SSLContexts
            .custom()
            // load identity keystore
            .loadKeyMaterial(identityKeyStore, "identity_password".toCharArray(), new PrivateKeyStrategy() {
                @Override
                public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) {
                    return "identity_alias";
                }
            })
            // load trust keystore
            .loadTrustMaterial(trustKeyStore, null)
            .build();

    SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
            new String[]{"TLSv1.2", "TLSv1.1"},
            null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());

    return HttpClients.custom()
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .build();
}

To build the identity.jks, you need the CAs chain, the public key and the private key:

要构建identity.jks,您需要 CA 链、公钥和私钥:

 = mycustomidentity

# make the keycert bundle for pkcs12 keystore
cat intermediate/certs/ca-chain.cert.pem \
    intermediate/certs/.cert.pem \
    intermediate/private/.key.pem \
    > intermediate/keycerts/.full-chain.keycert.pem

# generate the pkcs12 keystore with the alias of the server url
openssl pkcs12 -export \
    -in intermediate/keycerts/.full-chain.keycert.pem \
    -out intermediate/pkcs12s/.full-chain.p12 \
    -name  \
    -noiter -nomaciter

# .p12 to .jks
keytool -importkeystore -srckeystore .full-chain.p12 \
    -srcstoretype pkcs12 -srcalias  \
    -destkeystore identity.jks -deststoretype jks \
    -deststorepass identity_password -destalias identity_alias

For the trust.jksfile you only need the certificate of the server (see https://stackoverflow.com/a/36427118/2692914or https://stackoverflow.com/a/7886248/2692914), there is no problem in changing the alias:

对于trust.jks文件,你只需要在服务器(见证书https://stackoverflow.com/a/36427118/2692914https://stackoverflow.com/a/7886248/2692914),有改变的别名没有问题:

# .crt, .cer into a .jks
keytool -import -alias trust_alias -file server_certificate.crt \
    -keystore trust.jks