How do I pass the client certificate with HTTP client?

倾然丶 夕夏残阳落幕 提交于 2019-12-03 03:31:40

You need to tell an SSLSocketFactory (org.apache.http, not javax) about your keystore, and configure your DefaultHTTPClient to use it for https connections.

An example is here: http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientCustomSSL.java

Alain O'Dea

The client certificate is sent during the TLS handshake when establishing a connection and can't be sent via HTTP within that connection.

The communication is layered like this:

  • HTTP (application-layer protocol) within
  • TLS (presentation-layer protocol) within
  • TCP (transport-layer protocol) within
  • IP (network-layer protocol)

You need to send the client certificate during the TLS handshake before anything HTTP (methods, headers, URLs, request bodies) is available to be influenced. The server will not accept a client certificate sent later.

I'm recommending switching from DefaultHttpClient (deprecated) to CloseableHttpClient which works more cleanly with try-with-resources.

Apache HttpClient 4.5 makes Mutual TLS reasonably convenient. This answer has been tested with Apache HttpClient 4.5.3.

The essential starting point is using loadKeyMaterial to load your client certicate and it's key (the client keypair) into the SSLContext:

SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                password, password,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();

And finally building an HTTP client with that socket factory:

CloseableHttpClient httpclient = HttpClients
        .custom().setSSLContext(sslContext).build();

With that client, all your requests can be executed with Mutual TLS authentication implied:

CloseableHttpResponse closeableHttpResponse = httpclient.execute(
        new HttpGet(URI.create("https://mutual-tls.example.com/")));

Here's a full runnable example of mutual TLS with Apache HttpClient:

import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import java.io.Console;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;

public class MutualHttpsMain {
    private static final String TEST_URL = "https://mutual-tls.example.com/";
    private static final String TEST_CLIENT_KEYSTORE_RESOURCE = "/mutual-tls-keystore.p12";

    public static void main(String[] args) throws GeneralSecurityException, IOException {
        Console console = System.console();
        char[] password = console.readPassword("Keystore password: ");
        SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                password, password,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();
        try (CloseableHttpClient httpclient = HttpClients
                .custom().setSSLContext(sslContext).build();
             CloseableHttpResponse closeableHttpResponse = httpclient.execute(
                    new HttpGet(URI.create(TEST_URL)))) {
            console.writer().println(closeableHttpResponse.getStatusLine());
            HttpEntity entity = closeableHttpResponse.getEntity();
            try (InputStream content = entity.getContent();
                 ReadableByteChannel src = Channels.newChannel(content);
                 WritableByteChannel dest = Channels.newChannel(System.out)) {
                ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
                while (src.read(buffer) != -1) {
                    buffer.flip();
                    dest.write(buffer);
                    buffer.compact();
                }
                buffer.flip();
                while (buffer.hasRemaining())
                    dest.write(buffer);
            }
        }
    }
}

It's generally better to use Gradle or Maven to run something like this, but in the interest of keeping this Yak shave as minimal as possible I'm providing baseline JDK instructions for building and running this.

Download JARs from the following pages:

Save the full example above as MutualHttpsMain.java.

Copy your PKCS#12 to mutual-tls-keystore.p12 in the same directory.

Compile it as follows (on macOS/Linux/*nix-likes):

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar:httpcore-4.4.8.jar

Or on Windows:

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar;httpcore-4.4.8.jar

Run as follows (on macOS/Linux/*nix-likes):

java -cp httpclient-4.5.3.jar:commons-codec-1.10.jar:commons-logging-1.2.jar:httpcore-4.4.8.jar:. MutualHttpsMain

Run as follows (on Windows):

java -cp httpclient-4.5.3.jar;commons-codec-1.10.jar;commons-logging-1.2.jar;httpcore-4.4.8.jar;. MutualHttpsMain
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!