如何在Java中使用Webclient添加CA证书和客户端证书

业余PCB

我正在尝试使用Java中的Webclient编写API调用。目前,我在查找有关如何向Webclient添加证书的文档方面遇到麻烦。我既要提供PEM格式的CA证书文件,又要提供其中将提供主机,CRT文件,密钥文件和密码短语的客户端证书。我已经在邮递员中使用了此设置,但是我想将其转移到Java应用程序中。下面是我的代码。

       Gson gson = new Gson();
       LinkedHashMap<String, Object> reqBody
               = new LinkedHashMap<String, Object>();
       LinkedHashMap<String, String> variables
               = new LinkedHashMap<String, String>();
       reqBody.put("variables", variables);



       WebClient webClient = WebClient.builder()
               .baseUrl("sampleurl.com")
               .defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
               .defaultHeader(HttpHeaders.ACCEPT, "application/json")
               .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
               .build();


       return webClient.post()
               .uri("/api")
               .headers(headers -> headers.setBasicAuth("userName", "password"))
               .body(Mono.just(reqBody), LinkedHashMap.class)//if directly putting the map doesn't work
               //can also convert to json string then to monoflux
               .retrieve()
               .bodyToMono(String.class);
哈坎54

尽管michalk为Web客户端的示例ssl配置提供了链接,但仍未回答如何加载CA证书,密钥和密码的问题。

如果只想加载pem格式的证书(ca和自己的受信任证书)证书,则可以使用jdk中可用的类。但是,您还希望将密钥材料作为pem文件加载,而这对于jdk中的默认类是不可能的。对于这种用例,我会推荐Bouncy Castle:

Maven的依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
</dependency>
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class App {

    private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
    private static final JcaPEMKeyConverter KEY_CONVERTER = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER);
    private static final String CERTIFICATE_TYPE = "X.509";
    private static final Pattern CERTIFICATE_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", Pattern.DOTALL);
    private static final String NEW_LINE = "\n";
    private static final String EMPTY = "";

    public static PrivateKey parsePrivateKey(String privateKeyContent, char[] keyPassword) throws IOException, PKCSException, OperatorCreationException {
        PEMParser pemParser = new PEMParser(new StringReader(privateKeyContent));
        PrivateKeyInfo privateKeyInfo = null;

        Object object = pemParser.readObject();

        while (object != null && privateKeyInfo == null) {
            if (object instanceof PrivateKeyInfo) {
                privateKeyInfo = (PrivateKeyInfo) object;
            } else if (object instanceof PEMKeyPair) {
                privateKeyInfo = ((PEMKeyPair) object).getPrivateKeyInfo();
            } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
                InputDecryptorProvider inputDecryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder()
                        .setProvider(BOUNCY_CASTLE_PROVIDER)
                        .build(Objects.requireNonNull(keyPassword));

                privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(inputDecryptorProvider);
            } else if (object instanceof PEMEncryptedKeyPair) {
                PEMDecryptorProvider pemDecryptorProvider = new JcePEMDecryptorProviderBuilder()
                        .setProvider(BOUNCY_CASTLE_PROVIDER)
                        .build(keyPassword);

                PEMKeyPair pemKeyPair = ((PEMEncryptedKeyPair) object).decryptKeyPair(pemDecryptorProvider);
                privateKeyInfo = pemKeyPair.getPrivateKeyInfo();
            }

            if (privateKeyInfo == null) {
                object = pemParser.readObject();
            }
        }

        if (Objects.isNull(privateKeyInfo)) {
            throw new IllegalArgumentException("Received an unsupported private key type");
        }

        return KEY_CONVERTER.getPrivateKey(privateKeyInfo);
    }

    public static List<Certificate> parseCertificate(String certificateContent) throws IOException, CertificateException {
        List<Certificate> certificates = new ArrayList<>();
        Matcher certificateMatcher = CERTIFICATE_PATTERN.matcher(certificateContent);

        while (certificateMatcher.find()) {
            String sanitizedCertificate = certificateMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
            try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate)) {
                CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
                Certificate certificate = certificateFactory.generateCertificate(certificateAsInputStream);
                certificates.add(certificate);
            }
        }

        return certificates;
    }

    public static <T extends Certificate> KeyStore createTrustStore(List<T> certificates) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
        KeyStore trustStore = createEmptyKeyStore();
        for (T certificate : certificates) {
            trustStore.setCertificateEntry(UUID.randomUUID().toString(), certificate);
        }
        return trustStore;
    }

    public static <T extends Certificate> KeyStore createKeyStore(PrivateKey privateKey, char[] privateKeyPassword, List<T> certificateChain) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
        KeyStore keyStore = createEmptyKeyStore();
        keyStore.setKeyEntry(UUID.randomUUID().toString(), privateKey, privateKeyPassword, certificateChain.toArray(new Certificate[0]));
        return keyStore;
    }

    public static KeyStore createEmptyKeyStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        return keyStore;
    }

    public static void main(String[] args) throws PKCSException, OperatorCreationException, IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
        String certificateChainContent = "";
        String privateKeyContent = "";
        char[] privateKeyPassword = "secret".toCharArray();

        String caCertificateContent = "";

        PrivateKey privateKey = parsePrivateKey(privateKeyContent, privateKeyPassword);
        List<Certificate> certificates = parseCertificate(certificateChainContent);

        List<Certificate> caCertificates = parseCertificate(caCertificateContent);

        KeyStore keyStore = createKeyStore(privateKey, privateKeyPassword, certificates);
        KeyStore trustStore = createTrustStore(caCertificates);

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, null);

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        SslContext sslContext = SslContextBuilder.forClient()
                .keyManager(keyManagerFactory)
                .trustManager(trustManagerFactory)
                .build();

        HttpClient httpClient = HttpClient.create()
                .secure(sslSpec -> sslSpec.sslContext(sslContext));

        WebClient webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }

}

您可以尝试一下并分享结果吗?

我知道上面的代码片段有些冗长,因此,如果您不想包含所有内容,我还可以为您提供一种可以达到相同目的的替代方法:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-pem</artifactId>
    <version>5.3.0</version>
</dependency>
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import nl.altindag.sslcontext.util.PemUtils;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCSException;
import reactor.netty.http.client.HttpClient;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

class App {
    
    
    public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, OperatorCreationException, PKCSException {
        X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("certificateChain.pem", "private-key.pem", "secret".toCharArray());
        X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial("ca-certificates.pem");

        SslContext sslContext = SslContextBuilder.forClient()
                .keyManager(keyManager)
                .trustManager(trustManager)
                .build();

        HttpClient httpClient = HttpClient.create()
                .secure(sslSpec -> sslSpec.sslContext(sslContext));

        WebClient webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }
}

它使用与我在第一个代码段中共享的内容相同的代码段。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在客户端Java应用程序中使用客户端证书?

如何在GO中使用CA证书创建TLS客户端?

如何在根CA下生成服务器证书和客户端证书

如何在HTTPS中使用客户端证书?

如何在 JAX-WS 客户端和客户端证书认证上指定客户端证书

使用现有的中间 CA 密钥和证书与 keytool 生成客户端证书

在WebJob中使用客户端证书

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

如何在Scrapy中使用SSL客户端证书(p12)?

如何在节点中使用客户端证书进行HTTPS GET

如何在Spring Feign Client中使用P12客户端证书

如何在.Net Core中使用客户端SSL证书

如何仅从特定CA请求客户端证书

CA 续订后的客户端证书

如何使用Azure API管理入站策略中的根CA证书来验证我的客户端证书?

如何为使用OpenSSL创建客户端证书指定CA私钥密码

如何以编程方式获取用于验证客户端证书的 CA 证书

客户端证书 - 我应该使用自签名还是 CA 颁发的证书?

如何在Spring-Boot中使用SSL证书并为Android客户端生成公钥

如何在apache-commons net中使用生成的证书配置客户端身份验证

在Alamofire 2.0中使用客户端证书

在Curl命令中使用客户端证书

在 Jenkins 中使用 withCredentials 绑定中的客户端证书

无法在Tornado中使用SSL客户端证书

如何在Node-Opcua服务器中获得已连接的客户端和客户端证书

如何在Azure AppServices中使客户端证书身份验证为可选

添加客户端证书后,使用 Python 和 Postman 在 Windows 上“无法获得本地颁发者证书”

如何在春季WS客户端请求中添加证书链

如何在W7的Postman Chrome中添加客户端pkcs12证书?