当前位置: 首页 > 面试题库 >

如何在Apache HttpClient中使用SSL客户端证书?

章永安
2023-03-14
问题内容

在Python中,我使用了这样的请求:

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

非常简单。

现在,我需要使用Apache HttpClient在Java中实现相同的功能。使用HttpClient进行请求时如何通过客户端证书?


问题答案:

我认为主要区别在于,在Java中,通常将密钥和证书放在密钥存储区中,然后从那里使用它。就像您提到的那样,人们经常希望为此使用单独的库,例如提到的httpcomponents客户端(就像您在python示例中使用请求库一样)。

这是一个使用前面提到的库使用密钥库中的客户机证书的示例:

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用于依赖版本:

<?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>

我还发布了一个简单的测试页,用于测试客户端证书。

只是为了证明它是可以做到的,下面是一个示例,该示例仅使用标准Java API来使用客户端证书,而没有额外的库。

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);
            }
        }
    }
}

这是代码最少的第三个版本,但是它依赖于以下事实:a)密钥库是磁盘上的文件,而不是jar中的文件,并且b)密钥密码 必须 与密钥库密码相同。

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);
            }
        }
    }
}

当然,也可以将上面在代码中设置的属性指定为启动参数-Djavax.net.ssl.keyStore=/full/path/to/clientkeystore- samepassword.jks-Djavax.net.ssl.keyStorePassword=keystorepass



 类似资料:
  • 问题内容: 使用Websphere MQ 8.x,我们是较大环境中的一个应用程序,并且是某些数据接口的客户端。我们的应用程序是在WildFly 9上运行的Java EE应用程序,该应用程序使用资源适配器()与EAR文件一起部署在同一AS中。我们在两个方向上与MQ服务器进行交互。因此,一方面,我们有一些MDB(由于历史渊源仍是EJB 2.x格式而没有注释)列出了一些队列,并由包含激活配置属性的部署描

  • 问题内容: 我们的系统与多个Web服务提供商进行通信。它们都是从单个Java客户端应用程序调用的。到目前为止,所有的Web服务都已通过SSL进行,但是没有一个使用客户端证书。好吧,一个新的合作伙伴正在改变这一现状。 使应用程序使用证书进行调用很容易;设置和将做到这一点。但是,现在的问题是如何制作它,使其仅在调用该特定Web服务时使用证书。我想更一般地说,我们希望能够选择要使用的客户端证书(如果有)

  • 问题内容: 我目前在服务器具有默认的自签名SSL证书的测试环境中工作。我正在使用Restlet 2.1-RC2并实例化客户端资源,如下所示: 并在每次打来的电话中重复使用客户端。如何设置客户端,使其忽略有问题的证书。 问题答案: 正确的方法 是使用以下 方法 将此自签名证书导入客户端的信任库: 您可以直接在可能缺乏灵活性的JRE信任库()中进行操作,也可以在自己的文件副本中进行操作,然后将该文件设

  • 问题内容: 在我的Java应用程序中,我需要使用SSL连接到同一主机,但每次都使用不同的证书。我需要使用其他证书的原因是,远程站点使用嵌入在证书中的用户ID属性来标识客户端。 这是一个在3个不同的操作系统上运行的服务器应用程序,我需要能够在不重新启动进程的情况下切换证书。 问题答案: SSL可以向客户端提示要显示的证书。这 可能 使您可以使用一个具有多个身份的密钥存储,但是,不幸的是,大多数服务器

  • 问题内容: 我有以下代码使用SSL将服务器与客户端连接,现在我想添加客户端身份验证: (我有一个服务器密钥库(JCEKS类型)和一个客户端密钥库(JKS类型),服务器使用了一个信任库(证书),在其中我导入了两个证书,因为我也想使用此信任库进行客户端身份验证) 客户代码: 服务器代码: 在此先感谢您的帮助。 编辑:我在服务器端添加此代码: 但是,如果我删除cacerts中的客户端证书,则连接不会给我

  • 问题内容: 我想要求基于Java 1.7内置HttpsServer的服务器进行客户端证书认证。 我似乎找不到任何使服务器无法通过身份验证的方法。无论客户端证书是受信任的,未知的还是完全不存在的,它都会愉快地为所有旧客户端提供数据。 我对文档的阅读建议,如果不信任客户端,则设置HttpsParameters.setNeedClientAuth(true)应该会导致身份验证失败。我发现有类似问题的人的