Java HTTPS Client Certificate Authentication – Java HTTPS客户端证书认证

最后修改: 2021年 12月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

HTTPS is an extension of HTTP that allows secure communications between two entities in a computer network. HTTPS uses the TLS (Transport Layer Security) protocol to achieve secure connections.

HTTPS是HTTP的一个扩展,允许计算机网络中的两个实体之间进行安全通信。HTTPS使用TLS(传输层安全)协议来实现安全连接。

TLS can be implemented with one-way or two-way certificate verification. In the one-way, the server shares its public certificate so the client can verify that it’s a trusted server. The alternative is two-way verification. Both the client and the server share their public certificates to verify each other’s identity.

TLS可以通过单向或双向的证书验证来实现。在单向中,服务器分享它的公共证书,以便客户可以验证它是一个受信任的服务器。另一个选择是双向验证。客户端和服务器都分享他们的公共证书以验证对方的身份

This article will focus on two-way certificate verification, where the server will also check the client’s certificate.

本文将重点介绍双向证书验证,即服务器也将检查客户的证书

2. Java and TLS Versions

Java和TLS版本

TLS 1.3 is the latest version of the protocol. This version is more performant and secure. It has a more efficient handshake protocol and uses modern cryptographic algorithms.

TLS 1.3是该协议的最新版本。这个版本的性能和安全性更高。它有一个更有效的握手协议,并使用现代加密算法。

Java started supporting this version of the protocol in Java 11. We will use this version to generate certificates and implement a simple client-server pair that uses TLS to authenticate each other.

Java在Java 11中开始支持这个版本的协议。我们将使用这个版本来生成证书,并实现一个简单的客户端-服务器对,使用TLS来验证对方。

3. Generating Certificates in Java

3.在Java中生成证书

Since we’re doing a two-way TLS authentication, we’ll need to generate certificates for the client and the server.

由于我们正在进行双向的TLS认证,我们需要为客户和服务器生成证书。

In a production environment, it’s recommended to purchase the certificates from a Certificate Authority. However, for testing or demo purposes, it’s good enough to use self-signed certificates. For this article, we’re going to use Java’s keytool to generate the self-signed certificates.

在生产环境中,建议从一个证书颁发机构购买证书。然而,为了测试或演示的目的,使用自签名证书已经足够好了。在这篇文章中,我们将使用Java的keytool来生成自签名证书。

3.1. Server Certificate

3.1.服务器证书

First, we generate the server key store:

首先,我们生成服务器密钥存储

keytool -genkey -alias serverkey -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore serverkeystore.p12 -storepass password -ext san=ip:127.0.0.1,dns:localhost

We use the keytool -ext option to set the Subject Alternative Names (SAN) to define the local hostname/IP address that identifies the server. In general, we can specify multiple addresses with this option. However, the clients will be constrained to use one of these addresses to connect to the server.

我们使用keytool -ext选项来设置主题替代名称(SAN),以定义识别服务器的本地主机名/IP地址。一般来说,我们可以用这个选项指定多个地址。然而,客户端将被限制在使用这些地址中的一个来连接到服务器。

Next, we export the certificate to the file server-certificate.pem:

接下来,我们把证书导出到文件server-certificate.pem

keytool -exportcert -keystore serverkeystore.p12 -alias serverkey -storepass password -rfc -file server-certificate.pem

Finally, we add the server certificate to the client’s trust store:

最后,我们将服务器证书添加到客户端的信任商店

keytool -import -trustcacerts -file server-certificate.pem -keypass password -storepass password -keystore clienttruststore.jks

3.2. Client Certificate

3.2 客户端证书

Similarly, we generate the client key store and export its certificate:

同样地,我们生成客户端密钥存储并导出其证书。

keytool -genkey -alias clientkey -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore clientkeystore.p12 -storepass password -ext san=ip:127.0.0.1,dns:localhost

keytool -exportcert -keystore clientkeystore.p12 -alias clientkey -storepass password -rfc -file client-certificate.pem

keytool -import -trustcacerts -file client-certificate.pem -keypass password -storepass password -keystore servertruststore.jks

In the last command, we added the client’s certificate to the server trust store.

在最后一条命令中,我们将客户的证书添加到服务器信任存储中

4. Server Java Implementation

4.服务器Java实现

Using java sockets the server implementation is trivial. The SSLSocketEchoServer class gets a SSLServerSocket to easily support TLS authentication. We just need to specify  the cipher and protocols and the rest is just a standard echo server that replies the same messages that are sent by the client:

使用java套接字,服务器的实现是微不足道的。SSLSocketEchoServer类得到了一个SSLServerSocket,以轻松支持TLS认证。我们只需要指定密码和协议,其余的只是一个标准的回声服务器,回复与客户端发送的相同的消息。

public class SSLSocketEchoServer {

    static void startServer(int port) throws IOException {

        ServerSocketFactory factory = SSLServerSocketFactory.getDefault();
        try (SSLServerSocket listener = (SSLServerSocket) factory.createServerSocket(port)) {
            listener.setNeedClientAuth(true);
            listener.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256" });
            listener.setEnabledProtocols(new String[] { "TLSv1.3" });
            System.out.println("listening for messages...");
            try (Socket socket = listener.accept()) {
                
                InputStream is = new BufferedInputStream(socket.getInputStream());
                byte[] data = new byte[2048];
                int len = is.read(data);
                
                String message = new String(data, 0, len);
                OutputStream os = new BufferedOutputStream(socket.getOutputStream());
                System.out.printf("server received %d bytes: %s%n", len, message);
                String response = message + " processed by server";
                os.write(response.getBytes(), 0, response.getBytes().length);
                os.flush();
            }
        }
    }
}

The server listens for client connections. The invocation of the listener.setNeedClientAuth(true) requires the client to share its certificate with the server. In the background, the SSLServerSocket implementation authenticates the client using the TLS protocol.

服务器监听客户端的连接。调用listener.setNeedClientAuth(true)要求客户端与服务器共享其证书。在后台,SSLServerSocket实现使用TLS协议来验证客户端。

In our case, the self-signed client certificate is in the server trust store so that the socket will accept the connection. The server proceeds to read the message using the InputStream. It then uses the OuputStream to echo back the incoming message appending an acknowledgment.

在我们的例子中,自签的客户证书在服务器的信任存储中,因此套接字将接受连接。服务器继续使用InputStream读取消息。然后,它使用OuputStream来回传传入的消息,并附加一个确认信息。

5. Client Java Implementation

5.Java客户端的实现

In the same fashion as we did with the server, the client implementation is a simple SSLScocketClient class:

与我们处理服务器的方式相同,客户端的实现是一个简单的SSLScocketClient类。

public class SSLScocketClient {

    static void startClient(String host, int port) throws IOException {

        SocketFactory factory = SSLSocketFactory.getDefault();
        try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) {
            
            socket.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256" });
            socket.setEnabledProtocols(new String[] { "TLSv1.3" });
            
            String message = "Hello World Message";
            System.out.println("sending message: " + message);
            OutputStream os = new BufferedOutputStream(socket.getOutputStream());
            os.write(message.getBytes());
            os.flush();
            
            InputStream is = new BufferedInputStream(socket.getInputStream());
            byte[] data = new byte[2048];
            int len = is.read(data);
            System.out.printf("client received %d bytes: %s%n", len, new String(data, 0, len));
        }
    }
}

First, we create an SSLSocket that establishes a connection with the server. In the background, the socket will set up the TLS connection establishment handshake. As part of this handshake, the client will verify the server’s certificate and check that it’s in the client truststore.

首先,我们创建一个SSLSocket,与服务器建立一个连接。在后台,该套接字将设置TLS连接建立握手。作为该握手的一部分,客户端将验证服务器的证书,并检查它是否在客户端的truststore中。

Once the connection has been successfully established, the client sends a message to the server using the output stream. It then reads the server’s response with the input stream.

一旦连接成功建立,客户端就用输出流向服务器发送一个消息。然后,它用输入流读取服务器的响应。

6. Running the Applications

6.运行应用程序

To run the server, open a command window and run:

要运行服务器,请打开一个命令窗口并运行。

java -Djavax.net.ssl.keyStore=/path/to/serverkeystore.p12 \ 
  -Djavax.net.ssl.keyStorePassword=password \
  -Djavax.net.ssl.trustStore=/path/to/servertruststore.jks \ 
  -Djavax.net.ssl.trustStorePassword=password \
  com.baeldung.httpsclientauthentication.SSLSocketEchoServer

We specify the system properties for javax.net.ssl.keystore and javax.net.ssl.trustStore to point to the serverkeystore.p12 and servertruststore.jks files that we created before with keytool.

我们为javax.net.ssl.keystorejavax.net.ssl.trustStore指定系统属性,使之指向我们之前用keytool创建的serverkeystore.p12servertruststore.jks文件。

To run the client, we open another command window and run:

为了运行客户端,我们打开另一个命令窗口并运行。

java -Djavax.net.ssl.keyStore=/path/to/clientkeystore.p12 \ 
  -Djavax.net.ssl.keyStorePassword=password \ 
  -Djavax.net.ssl.trustStore=/path/to/clienttruststore.jks \ 
  -Djavax.net.ssl.trustStorePassword=password \ 
  com.baeldung.httpsclientauthentication.SSLScocketClient	

Similarly, we set the javax.net.ssl.keyStore and javax.net.ssl.trustStore system properties to point to the clientkeystore.p12 and clienttruststore.jks files that we generated before with keytool.

同样,我们设置javax.net.ssl.keyStorejavax.net.ssl.trustStore系统属性来指向我们之前用keytool生成的clientkeystore.p12clienttruststore.jks文件。

7. Conclusion

7.结语

We’ve written a simple client-server Java implementation that uses server and client certificates to do a bidirectional TLS authentication.

我们写了一个简单的客户端-服务器的Java实现,使用服务器和客户端的证书来做双向的TLS认证

We used keytool to generate the self-signed certificates.

我们使用keytool来生成自签名的证书。

The source code of the examples can be found over on GitHub.

这些例子的源代码可以在GitHub上找到