使用Java执行mTLS调用
之前我们使用SSL和mTLS来保护Nginx实例。如果你使用的是Java,与使用mTLS的服务进行交互需要对你的代码库进行一些修改。在本教程中,我们将使我们的Java应用程序使用不同的客户端来使用mTLS。
为了快速入门,我们将按照我们在mTLS博客上所做的完全相同的方式启动一个服务器。这将使事情变得简单,而且客户端的证书也将到位。
为了对我们的Java客户端进行ssl配置,我们需要首先设置一个SSLContext。这就简化了事情,因为这个SSLContext可以用于各种http客户端。
由于我们有客户端的公钥和私钥,我们需要将私钥从PEM格式转换为DER格式。
openssl pkcs8 -topk8 -inform PEM -outform PEM -in /path/to/generated/client.key -out /path/to/generated/client.key.pkcs8 -nocrypt
在这个例子中,通过使用本地Nginx服务,我们需要禁用主机名验证。
final Properties props = System.getProperties();
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
在其他客户端,这可能需要设置一个接受所有连接的HostVerifier。
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
下一步是将客户端的密钥加载到java代码中,并创建一个KeyManagerFactory。
String privateKeyPath = "/path/to/generated/client.key.pkcs8";
String publicKeyPath = "/path/to/generated/client.crt";
final byte[] publicData = Files.readAllBytes(Path.of(publicKeyPath));
final byte[] privateData = Files.readAllBytes(Path.of(privateKeyPath));
String privateString = new String(privateData, Charset.defaultCharset())
.replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PRIVATE KEY-----", "");
byte[] encoded = Base64.getDecoder().decode(privateString);
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
final Collection<? extends Certificate> chain = certificateFactory.generateCertificates(
new ByteArrayInputStream(publicData));
Key key = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(encoded));
KeyStore clientKeyStore = KeyStore.getInstance("jks");
final char[] pwdChars = "test".toCharArray();
clientKeyStore.load(null, null);
clientKeyStore.setKeyEntry("test", key, pwdChars, chain.toArray(new Certificate[0]));
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(clientKeyStore, pwdChars);
在上面的片段中
- 我们从文件中读取字节。
- 我们从公钥中创建了一个证书链。
- 我们用私钥创建了一个密钥实例。
- 使用证书链和钥匙创建了一个钥匙库
- 创建了一个KeyManagerFactory
现在我们已经创建了一个KeyManagerFactory,我们可以用它来创建一个SSLContext。
由于使用自签名的证书,我们需要使用一个可以接受它们的TrustManager。在这个例子中,信任管理器将接受来自服务器的所有证书。
TrustManager[] acceptAllTrustManager = {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(
X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
X509Certificate[] certs, String authType) {
}
}
};
然后进行SSL上下文的初始化。
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), acceptAllTrustManager, new java.security.SecureRandom());
让我们使用一个客户端,看看它是如何表现的
HttpClient client = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
HttpRequest exactRequest = HttpRequest.newBuilder()
.uri(URI.create("https://127.0.0.1"))
.GET()
.build();
var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
.join();
System.out.println(exactResponse.statusCode());
我们将收到一个404代码(Nginx安装的默认值),这意味着我们的请求已经成功进行了mTLS握手。
现在让我们试试另一个客户端,老式的同步HttpsURLConnection。请注意。我使用之前创建的allHostsValid。
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) new URL("https://127.0.0.1").openConnection();
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
httpsURLConnection.setHostnameVerifier(allHostsValid);
InputStream inputStream = httpsURLConnection.getInputStream();
String result = new String(inputStream.readAllBytes(), Charset.defaultCharset());
这将抛出一个404错误,这意味着握手成功了。
因此,无论你是有一个异步http客户端还是同步客户端,只要你配置了正确的SSLContext,你就应该能够进行握手。
