Java 通过证书访问Https服务

前言

有些HTTP 服务要求客户端在请求的时候使用证书加密/解密/授权 (客户端证书授权)。比如说下面的 curl 命令

curl --insecure \
  --cert cert.crt \ 
  --key key.key \
  -d '{"name":"joel"}' \
  -H "Content-Type: application/json" \
  -X POST https://localhost:13321/get_user_info

那么,在Java中如何实现呢?

搜索关键词

  1. Java HTTPS client certificate authentication
  2. PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
  3. Certificate for <xxx.xxx.xxx.xxx> doesn’t match any of the subject alternative names: [xxx.xxx]
  4. Chia RPC Java

证书转换

将 crt 和 key 导出为 p12

openssl pkcs12 -export -in cert.crt -inkey key.key -out cert.p12 -name "joelzho.com"

备注:

  1. 将输出的密码记住
  2. -name 的值最好是访问该服务接口的域名

示例代码

示例代码环境

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.junit.Test;

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

/**
 * @author Joel
 */
public class TestClientCertAuthHtttp {
    @Test
    public void test() throws Exception {
        final String certFile = "/path/to/cert.p12";
        final String storePass = "store-pass";
        final String keyPass = "key-pass";

        KeyStore keyStore;
        try (FileInputStream fis = new FileInputStream(certFile)) {
            keyStore = KeyStore.getInstance("PKCS12"); // or "JKS"
            keyStore.load(fis, storePass.toCharArray());
        }

        SSLContextBuilder sslContextBuilder = SSLContexts.custom();
        sslContextBuilder.loadTrustMaterial(TrustAllStrategy.INSTANCE);
        sslContextBuilder.loadKeyMaterial(keyStore, keyPass.toCharArray());
        SSLContext sslContext = sslContextBuilder.build();

        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        httpClientBuilder.setSSLContext(sslContext);
        httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
        CloseableHttpClient httpClient = httpClientBuilder.build();

        // ....
    }
}

代码解读

密码

p12 (PKCS12) 证书只有一个密码,即 storePasskeyPass 相等.

如果是 jks 证书的话,两个密码大概率不同(看你导出jks证书的时候怎么设置的)

loadTrustMaterial

sslContextBuilder.loadTrustMaterial(TrustAllStrategy.INSTANCE); 这句代码主要是用来解决自签名证书不被Java信任的问题。相关的类有

  1. TrustStrategy: 一个接口
  2. TrustAllStrategy: 信任所有
  3. TrustSelfSignedStrategy: 信任自签名证书

这里选择 TrustAllStrategy 的原因是 TrustSelfSignedStrategy 的实现在开发环境可能无效(比如说不小心将证书导入到系统中了,然后你代码中又指定了一遍,详情看该类的实现代码)。

当然,最安全的做法是自己实现 TrustStrategy 在里面写验证。

相关异常:PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

setSSLHostnameVerifier

httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); 这句代码主要是用来忽略证书里面的 host实际请求的 host 不一致验证。

也许证书颁发机构给的证书里面写定的是一个域名,但是你本机做测试的时候没有域名或这个证书被用在不同的服务里面。那么加上这个句代码即可。

相关异常:Certificate for <xxx.xxx.xxx.xxx> doesn’t match any of the subject alternative names: [xxx.xxx]

番外

jks 证书怎么导出?

在这个示例中,你要先有一个 p12 格式的证书 (见上文)

keytool -importkeystore \
  -deststorepass storepassword \
  -destkeypass keypassword \
  -destkeystore cert.jks \
  -srckeystore cert.p12 \
  -srcstoretype PKCS12 \
  -srcstorepass srcstorepass \
  -alias joelzho.com

备注:

  1. deststorepassdestkeypass 的值要用到代码中
  2. srcstorepass 是 p12证书的密码
  3. -alias 的值最好是访问该服务接口的域名
  4. 获取 KeyStore实例的代码修改为: KeyStore.getInstance("JKS");

参考链接

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值