在本教程中,我们将学习如何通过使用不同的客户端使我们的Java应用程序能够使用mTLS。我们将使用将mTLS添加到Nginx实例的现有示例。
假设我们有一个Nginx实例,使用SSL还有。如果使用Java与使用mTLS保护的服务进行交互,则需要对代码库进行一些更改。在本教程中,我们将使Java应用程序能够使用不同客户端的mTLS。
要快速入门,我们可以使用现有的示例添加MTLS一个Nginx实例。我们的java mTLS配置将使用用于将mTLS添加到Nginx的证书和密钥。
为了为我们的Java客户端进行SSL配置,我们需要首先设置一个SSLContext。这简化了一些事情,因为SSLContext可以用于现有的各种http客户机。
因为我们有客户端的公钥和私钥,所以我们需要将私钥从PEM格式转换为der。
kcs8 -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);
在上面的片段中:
- 我们从文件中读取字节。
- 我们从公钥创建了一个证书链。
- 我们使用私钥创建了一个密钥实例。
- 使用链和键创建了一个Keystore
- 创建了一个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,您都应该能够进行握手。
小伙伴们如果觉得我写的不错,不妨帮个忙,给我点赞+收藏,可以让更多的人看到这篇文章! 完整资料已经给大家打包完毕,完整资料已经给大家打包完毕,需要的小伙伴可以点击获取学习资料