如何在 Java 中使用 Webclient 添加 CA 证书和客户端证书
How to add both a CA certificate and a Client Certificate using Webclient in Java
我正在尝试在 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);
尽管 michalk 提供了一个 link 用于 web 客户端的示例 ssl 配置,但关于如何加载 CA 证书、密钥和密码的问题仍然没有得到解答。
如果您只想加载 pem 格式的(ca 和自己的可信证书)证书,您可以使用 jdk 中可用的 类。但是您还想将密钥 material 作为 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();
}
}
它使用我在第一个代码片段中共享的相同代码片段。
我正在尝试在 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);
尽管 michalk 提供了一个 link 用于 web 客户端的示例 ssl 配置,但关于如何加载 CA 证书、密钥和密码的问题仍然没有得到解答。
如果您只想加载 pem 格式的(ca 和自己的可信证书)证书,您可以使用 jdk 中可用的 类。但是您还想将密钥 material 作为 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();
}
}
它使用我在第一个代码片段中共享的相同代码片段。