使用Java执行mTLS调用方法介绍

491 阅读3分钟

使用Java执行mTLS调用

之前我们使用SSLmTLS来保护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,你就应该能够进行握手。