本教程展示使用 Java作为客户端 与受 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
|
下一步是将客户端密钥加载到 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 代码,这意味着我们的请求成功进行了 mTLS 握手。
注意:如果服务器端是使用本地 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; } };
|