掘金 后端 ( ) • 2024-04-27 14:40

目的

数据加密的方式有很多,主要分有对称加密和非对称加密,为了保证数据的安全,我们往往使用的是非对称加密,就比如HTTPS就是使用TLS非对称加密,我们这里就来简单实现一下TLS的加密隧道,并且添加信任自己生成的SSL证书。

HTTPS协议

HTTPS是一种通过计算机网络进行安全通信的协议。它是基于HTTP的加密协议,用于在网络中安全地传输数据。HTTPS通过使用TLS或其前身SSL来加密通信内容,以确保数据在传输过程中的保密性和完整性。

SSL证书

我们可以通过java的keytool生成证书,重点是-ext配置的域名和ip,不然浏览器会提示不安全。

# 生成密钥对,包含有效期365天,-ext 设置域名
keytool -genkeypair -alias mycert -keyalg RSA -keysize 2048 -keystore keystore.jks -validity 365 -ext SAN=dns:www.test.com,ip:192.168.35.124
# 生成证书请求
keytool -certreq -alias mycert -keystore keystore.jks -file server.csr
# 生成自签名证书
keytool -gencert -alias mycert -keystore keystore.jks -infile server.csr -outfile server.crt -validity 365

QQ截图20240427111751.png

安装证书

双击上面生成的server.crt安装证书,添加到受信任的根证书颁发机构

QQ截图20240427111952.png

可以在Internet属性->内容->证书->受信任的根证书颁发机构中查看

QQ截图20240427112257.png

注意:证书更换要重新启动浏览器才生效。

Java代码

服务端

首先我们做实现一个带HTTPS的Web服务端,HTTP协议我们可以通过ServerSocket去完成协议,而HTTPS我们使用SSLServerSocket,加了KeyStoreSSLContext多了些SSL内容。下面代码就打印HTTP信息,并且返回Hello HTTPS!的HTTP报文。注意判断HTTP报文结束是两个换行,不然后会阻塞等待内容。

public class ServerMain {

    public static void main(String[] args) {

        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        try {
            //载入keystore
            char[] keystorePassword = "password".toCharArray();
            char[] keyPassword = "password".toCharArray();
            InputStream resourceAsStream = ServerMain.class.getClassLoader().getResourceAsStream("keystore.jks");
            KeyStore keystore = KeyStore.getInstance("JKS");
            keystore.load(resourceAsStream, keystorePassword);

            //创建和初始化SSLContext
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keystore, keyPassword);
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(keystore);
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

            //创建SSLServerSocket
            SSLServerSocketFactory factory = context.getServerSocketFactory();
            SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(12345);

            while (true) {
                //接受客户端连接
                SSLSocket socket = (SSLSocket) serverSocket.accept();
                // 在线程池中处理客户端连接
                executorService.submit(() -> {
                    try {
                        handleClient(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void handleClient(SSLSocket socket) throws IOException {

        try (
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
        ) {
            // 读取HTTP请求
            StringBuilder request = new StringBuilder();
            String line;

            while ((line = reader.readLine()) != null && !line.isEmpty()) {
                request.append(line).append("\r\n");
            }

            System.out.println("Received from client: " + request);

            // 写入HTTP响应
            String response = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html; charset=UTF-8\r\n" +
                    "Connection: close\r\n" + // Close connection after sending response
                    "\r\n" +
                    "Hello HTTPS!";

            writer.write(response);
            writer.flush();

        } finally {
            // 关闭 socket
            socket.close();
        }
    }
}

服务端目录结构

QQ截图20240427115747.png

通过浏览器输入地址:https://192.168.35.124:12345/访问测试

QQ截图20240427113343.png

尝试删掉证书,浏览器显示,java后台也报错了javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown

QQ截图20240427114233.png

客户端

客户端代码就简单发送了Hello from client后面加了两个换行保证了结束。

public class ClientMain {

    public static void main(String[] args) throws Exception {
        // 载入 truststore
        char[] truststorePassword = "password".toCharArray();
        KeyStore truststore = KeyStore.getInstance("JKS");
        InputStream resourceAsStream = ClientMain.class.getClassLoader().getResourceAsStream("keystore.jks");
        truststore.load(resourceAsStream, truststorePassword);

        // 创建和初始化 SSLContext
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(truststore);
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        // 创建 SSLSocket
        SSLSocketFactory factory = context.getSocketFactory();
        SSLSocket socket = (SSLSocket) factory.createSocket("127.0.0.1", 12345);

        try(BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));){
            writer.write("Hello from client\r\n\r\n");
            writer.flush();

            // 读取响应请求
            StringBuilder response = new StringBuilder();
            String line;

            while ((line = reader.readLine()) != null && !line.isEmpty()) {
                response.append(line).append("\r\n");
            }
            System.out.println("Received from server: " + response);

        }finally {
            // 关闭 socket
            socket.close();
        }

    }

}

客户端目录结构

QQ截图20240427120238.png

客户端控制台输出

QQ截图20240427120509.png

服务端控制台输出

QQ截图20240427120537.png

代码仓库

断续/TlsDemo (gitee.com)