我认为主要区别在于,在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。



