Wie verwende ich ein SSL-Client-Zertifikat mit Apache HttpClient?

Lesezeit: 6 Minuten

Benutzer-Avatar
Daniels

In Python habe ich Anfragen wie folgt verwendet:

requests.put(
              webdavURL, 
              auth=(tUsername, tPassword), 
              data=webdavFpb, 
              verify=False, 
              cert=("/path/to/file.pem", "/path/to/file.key"))

Kinderleicht.

Jetzt muss ich dasselbe in Java mit Apache HttpClient implementieren. Wie kann ich ein Client-Zertifikat übergeben, wenn ich Anfragen mit HttpClient mache?

Benutzer-Avatar
eis

Ich denke, der Hauptunterschied besteht darin, dass Sie in Java den Schlüssel und das Zertifikat normalerweise in einem Schlüsselspeicher ablegen und von dort aus verwenden. Wie Sie bereits erwähnt haben, möchten die Leute häufig eine separate Bibliothek dafür verwenden, wie erwähnt httpcomponents-Client (so wie du es verwendest Bibliothek anfordert in Ihrem Python-Beispiel).

Hier ist ein Beispiel für die Verwendung eines Client-Zertifikats aus einem Schlüsselspeicher unter Verwendung der zuvor erwähnten Bibliothek:

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyStore;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class MyClientCertTest {

    private static final String KEYSTOREPATH = "/clientkeystore.jks"; // or .p12
    private static final String KEYSTOREPASS = "keystorepass";
    private static final String KEYPASS = "keypass";

    KeyStore readStore() throws Exception {
        try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
            KeyStore keyStore = KeyStore.getInstance("JKS"); // or "PKCS12"
            keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());
            return keyStore;
        }
    }
    @Test
    public void readKeyStore() throws Exception {
        assertNotNull(readStore());
    }
    @Test
    public void performClientRequest() throws Exception {
        SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(readStore(), KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password
                .build();

        HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
        HttpResponse response = httpClient.execute(new HttpGet("https://slsh.iki.fi/client-certificate/protected/"));
        assertEquals(200, response.getStatusLine().getStatusCode());
        HttpEntity entity = response.getEntity();

        System.out.println("----------------------------------------");
        System.out.println(response.getStatusLine());
        EntityUtils.consume(entity);
    }
}

Maven Pom für Abhängigkeitsversionen:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.acme</groupId>
    <artifactId>httptests</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.9</version>
                <!-- this is not needed, but useful if you want to debug what's going
                     on with your connection -->
                <configuration>
                    <argLine>-Djavax.net.debug=all</argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Ich habe auch eine einfache veröffentlicht Testseite zum Testen eines Client-Zertifikats.


Nur um zu demonstrieren, dass dies möglich ist, finden Sie unten ein Beispiel für die Verwendung eines Client-Zertifikats, das nur die Standard-Java-API ohne zusätzliche Bibliotheken verwendet.

import org.junit.Test;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyStore;

public class PlainJavaHTTPS2Test {

    @Test
    public void testJKSKeyStore() throws Exception {
        final String KEYSTOREPATH = "clientkeystore.jks";
        final char[] KEYSTOREPASS = "keystorepass".toCharArray();
        final char[] KEYPASS = "keypass".toCharArray();

        try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
            setSSLFactories(storeStream, "JKS", KEYSTOREPASS, KEYPASS);
        }
        testPlainJavaHTTPS();
    }
    @Test
    public void testP12KeyStore() throws Exception {
        final String KEYSTOREPATH = "clientkeystore.p12";
        final char[] KEYSTOREPASS = "keystorepass".toCharArray();
        final char[] KEYPASS = "keypass".toCharArray();

        try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
            setSSLFactories(storeStream, "PKCS12", KEYSTOREPASS, KEYPASS);
        }
        testPlainJavaHTTPS();
    }
    private static void setSSLFactories(InputStream keyStream, String keystoreType, char[] keyStorePassword, char[] keyPassword) throws Exception
    {
        KeyStore keyStore = KeyStore.getInstance(keystoreType);

        keyStore.load(keyStream, keyStorePassword);

        KeyManagerFactory keyFactory =
                KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

        keyFactory.init(keyStore, keyPassword);

        KeyManager[] keyManagers = keyFactory.getKeyManagers();

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(keyManagers, null, null);
        SSLContext.setDefault(sslContext);
    }

    public void testPlainJavaHTTPS() throws Exception {
        String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";
        URL myUrl = new URL(httpsURL);
        HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
        try (InputStream is = conn.getInputStream()) {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            String inputLine;

            while ((inputLine = br.readLine()) != null) {
                System.out.println(inputLine);
            }
        }
    }
}

Und hier ist eine dritte Version mit der geringsten Menge an Code, die sich aber darauf stützt, dass a) keystore eine Datei auf der Festplatte ist, nicht innerhalb von jar, und b) das Schlüsselkennwort muss identisch mit dem Keystore-Passwort sein.

import org.junit.BeforeClass;
import org.junit.Test;

import java.net.URL;
import java.io.*;
import javax.net.ssl.HttpsURLConnection;

public class PlainJavaHTTPSTest {

    @BeforeClass
    public static void setUp() {
        System.setProperty("javax.net.ssl.keyStore", "/full/path/to/clientkeystore-samepassword.jks");
        System.setProperty("javax.net.ssl.keyStorePassword", "keystorepass");
    }

    @Test
    public void testPlainJavaHTTPS() throws Exception {
        String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";
        URL myUrl = new URL(httpsURL);
        HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
        try (InputStream is = conn.getInputStream()) {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            String inputLine;

            while ((inputLine = br.readLine()) != null) {
                System.out.println(inputLine);
            }
        }
    }
}

Die oben im Code gesetzten Eigenschaften können natürlich auch als Startparameter angegeben werden, -Djavax.net.ssl.keyStore=/full/path/to/clientkeystore-samepassword.jks und -Djavax.net.ssl.keyStorePassword=keystorepass.

  • Wenn der Schlüsselspeicher mehrere Schlüssel enthält, woher weiß der Code, welcher verwendet werden soll?

    – David Brossard

    23. August 2019 um 14:08 Uhr

  • @DavidBrossard stackoverflow.com/questions/23527426/…

    – eis

    24. August 2019 um 15:26 Uhr

  • Ich versuche seit Tagen, einen Zertifikatscode zum Laufen zu bringen. Ich habe diesen Code auf mehrere Arten ausprobiert und immer noch keine Wirkung. Ich habe keine jks- oder p12-Datei, nur die Datei /C/Program Files/Java/jdk1.8.0_271/jre/lib/security/cacerts – ich erhalte „PKIX-Pfaderstellung fehlgeschlagen: sun.security.provider.certpath. SunCertPathBuilderException: kein gültiger Zertifizierungspfad zum angeforderten Ziel gefunden” Ich habe viele SO-Posts ausprobiert -stackoverflow.com/questions/17712417/…; funktioniert dies nur mit .jks/.p12-Dateien; wie bekomme ich die? bitte helfen.

    – ASheppardWork

    2. April 2021 um 18:43 Uhr

  • @ASheppardWork hört sich so an, als hätten Sie kein Client-Zertifikat, wenn Sie nur eine cacerts-Datei haben, also hat es nichts mit dieser Frage oder diesem Code zu tun?

    – eis

    2. April 2021 um 21:24 Uhr

Benutzer-Avatar
Bosko Mijin

Wenn Sie den Apache-HTTP-Client anstelle des Java-HTTP-Clients verwenden möchten, müssen Sie SSLFactory Ihren Schlüsselspeicher bereitstellen und DefaultHTTPClient konfigurieren, um ihn im HTTPS-Protokoll zu verwenden.

Sie können ein funktionierendes Beispiel finden hier.

Ich hoffe das hilft.

  • Können Sie einige Hinweise zur Verwendung des Java HTTP Client geben? Ich habe Schwierigkeiten mit Apache One.

    – Ravi

    7. Juli 2016 um 6:18 Uhr


  • Ich kann mich irren, aber in diesem Beispiel scheint es nur darum zu gehen, einen benutzerdefinierten Truststore zu verwenden (z. B. um einem Zertifikat zu vertrauen, dem normalerweise nicht vertraut wird), anstatt ein Client-Zertifikat bereitzustellen, um den Client zu authentifizieren.

    – Matt Sheppard

    3. Mai 2017 um 7:10 Uhr

  • Dies beantwortet die Frage nicht. Dadurch wird nur ein benutzerdefinierter Truststore hinzugefügt, kein Client-Zertifikat.

    – stendal

    7. September 2017 um 13:47 Uhr

  • In diesem Beispiel geht es um einen benutzerdefinierten Trust Store. Hier wird nicht gezeigt, wie ein Clientzertifikat bereitgestellt wird.

    – Kanishka Dilshan

    21. September 2020 um 9:58 Uhr

1016070cookie-checkWie verwende ich ein SSL-Client-Zertifikat mit Apache HttpClient?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy