L.W.Kevin0wvf 2024-09-15 15:27 采纳率: 100%
浏览 21
已结题

java TLS连接问题

为什么Java服务端和客户端TLS连接时会出现以下报错?
客户端:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:365)
    at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:287)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:204)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1506)
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1421)
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:455)
    at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:922)
    at java.base/sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:1291)
    at java.base/java.io.ObjectOutputStream$BlockDataOutputStream.drain(ObjectOutputStream.java:1899)
    at java.base/java.io.ObjectOutputStream$BlockDataOutputStream.setBlockDataMode(ObjectOutputStream.java:1808)
    at java.base/java.io.ObjectOutputStream.<init>(ObjectOutputStream.java:256)
    at com.remote_connect.remote_connect.Client.get(Client.java:78)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:96)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:98)
    at com.remote_connect.remote_connect.Client.getUntilSucceed(Client.java:89)
    at com.remote_connect.remote_connect.Client.getHeartbeatInterval(Client.java:104)
    at Main.lambda$main$0(Main.java:17)
    at java.base/java.lang.Thread.run(Thread.java:1583)

服务端:

javax.net.ssl.SSLHandshakeException: No available authentication scheme
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:365)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:312)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.onProduceCertificate(CertificateMessage.java:967)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.produce(CertificateMessage.java:956)
    at java.base/sun.security.ssl.SSLHandshake.produce(SSLHandshake.java:437)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.goServerHello(ClientHello.java:1245)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.consume(ClientHello.java:1181)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.onClientHello(ClientHello.java:839)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.consume(ClientHello.java:800)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:393)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:476)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:447)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:201)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1506)
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1421)
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:455)
    at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:922)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1013)
    at java.base/java.io.ObjectInputStream$PeekInputStream.read(ObjectInputStream.java:2915)
    at java.base/java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2931)
    at java.base/java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3428)
    at java.base/java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:985)
    at java.base/java.io.ObjectInputStream.<init>(ObjectInputStream.java:416)
    at com.remote_connect.remote_connect.Server$ClientHandler.run(Server.java:81)
    at java.base/java.lang.Thread.run(Thread.java:1583)

证书:
证书均由openssl生成,未到期,客户端和服务端的jks文件中都包含了客户端和服务端两个证书及各自的密钥
客户端证书Subject信息:

Subject: EMAILADDRESS=XXX, CN=localhost, OU=FuturElectra, O=FuturElectra, L=Beijing, ST=Beijing, C=CN

服务端证书Subject信息:

Subject: EMAILADDRESS=XXX, CN=localhost, OU=FuturElectra, O=FuturElectra, L=Beijing, ST=Beijing, C=CN

客户端代码:

package com.remote_connect.remote_connect;

import com.windows.create_windows.ReconnectingWindow;

import javax.net.ssl.*;
import java.io.*;
import java.security.*;
import java.util.Map;
import java.util.concurrent.*;

public class Client {
    private static final String serverName = "localhost";
    private static final int port = 8443;
    private static SSLSocket client;
    private static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    public static void connect() throws Exception {
        System.out.println("连接到主机:‌" + serverName + " ,‌端口号:‌" + port);
        SSLContext sslContext = createSSLContext();
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        client = (SSLSocket) sslSocketFactory.createSocket(serverName, port);
        System.out.println("远程主机地址:‌" + client.getRemoteSocketAddress());
        client.setSoTimeout(20000);
    }

    private static SSLContext createSSLContext() throws IOException {
        try {
            //初始化密钥库
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            KeyStore keyStore = getKeyStore("client.jks", "123456", "JKS");
            keyManagerFactory.init(keyStore, "123456".toCharArray());

            //初始化信任库
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
            KeyStore trustkeyStore = getKeyStore("client.jks", "123456", "JKS");    //通常信任库和密钥库是同一个文件
            trustManagerFactory.init(trustkeyStore);

            //初始化SSL上下文
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext;
        } catch (Exception e) {
            throw new IOException("无法初始化SSL上下文", e);
        }
    }

    private static KeyStore getKeyStore(String keyStorePath, String password, String type) throws Exception {
        FileInputStream is = new FileInputStream(keyStorePath);
        KeyStore ks = KeyStore.getInstance(type);
        ks.load(is, password.toCharArray());
        is.close();
        return ks;
    }

    public static boolean reconnect() {
        ReconnectingWindow.window.setVisible(true);
        int maxAttempts = 20;
        int attempt = 0;
        while (attempt < maxAttempts) {
            try {
                connect();
                ReconnectingWindow.window.setVisible(false);
                return true;
            } catch (Exception ex) {
                System.err.println("连接失败: " + ex.getMessage());
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ignored) {}
            attempt++;
        }
        System.err.println("重连失败");
        ReconnectingWindow.window.setVisible(false);
        return false;
    }

    public static Map<?, ?> get(String msg, Object data) throws IOException, ClassNotFoundException {
        ObjectOutputStream out = new ObjectOutputStream(client.getOutputStream());
        out.writeObject(InformationProcessing.encrypt(Map.of("msg", msg, "data", data)));
        System.out.println(InformationProcessing.encrypt(Map.of("msg", msg, "data", data)));
        ObjectInputStream in = new ObjectInputStream(client.getInputStream());
        return InformationProcessing.decrypt((Map<?, ?>) in.readObject());
    }

    public static Map<?, ?> getUntilSucceed(String msg, Object data) throws IOException, ClassNotFoundException {
        try {
            return get(msg, data);
        } catch (IOException | ClassNotFoundException e) {
            if (reconnect()) return getUntilSucceed(msg, data, 15);
            else throw e;
        }
    }

    public static Map<?, ?> getUntilSucceed(String msg, Object data, int attempt) throws IOException, ClassNotFoundException {
        try {
            return get(msg, data);
        } catch (IOException | ClassNotFoundException e) {
            if (attempt > 0 && reconnect()) return getUntilSucceed(msg, data, attempt - 1);
            else throw e;
        }
    }

    public static int getHeartbeatInterval() throws IOException, ClassNotFoundException {
        return (int) getUntilSucceed("GetHeartbeatInterval", "").get("response");
    }

    public static void startSendingHeartbeat(int interval) {
        Runnable heartbeatTask = () -> {
            try {
                Map<?, ?> res = get("", "");    //发送心跳包
                if (!(boolean) res.get("response")) {
                    stopSendingHeartbeat();
                    reconnect();
                }
            } catch (Exception e) {
                stopSendingHeartbeat();
                reconnect();
            }
        };
        scheduler.scheduleAtFixedRate(heartbeatTask, 0, interval, TimeUnit.SECONDS);
    }

    public static void stopSendingHeartbeat() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
        }
    }
}

服务端代码:

package com.remote_connect.remote_connect;

import com.basic.basic.*;

import javax.net.ssl.*;
import java.io.*;
import java.security.*;
import java.util.Map;

public class Server {
    private final SSLServerSocket serverSocket;

    public Server(int port) throws IOException {
        SSLContext sslContext = createSSLContext();
        SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
        this.serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
        this.serverSocket.setSoTimeout(0);    //设置超时时间为0,‌表示不设置超时
        System.out.println("服务器启动成功!");
    }

    private SSLContext createSSLContext() throws IOException {
        try {
            //初始化密钥库
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            KeyStore keyStore = getKeyStore("server.jks", "123456", "JKS");
            keyManagerFactory.init(keyStore, "123456".toCharArray());

            //初始化信任库
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
            KeyStore trustkeyStore = getKeyStore("server.jks", "123456", "JKS");    //通常信任库和密钥库是同一个文件
            trustManagerFactory.init(trustkeyStore);

            //初始化SSL上下文
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext;
        } catch (Exception e) {
            throw new IOException("无法初始化SSL上下文", e);
        }
    }

    private KeyStore getKeyStore(String keyStorePath, String password, String type) throws Exception {
        FileInputStream is = new FileInputStream(keyStorePath);
        KeyStore ks = KeyStore.getInstance(type);
        ks.load(is, password.toCharArray());
        is.close();
        return ks;
    }

    public void close() {
        try {
            this.serverSocket.close();    //关闭服务器套接字
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void listen() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                SSLSocket clientSocket = (SSLSocket) this.serverSocket.accept();
                //为每个客户端连接创建一个新的线程
                new Thread(new ClientHandler(clientSocket)).start();
            } catch (IOException e) {
                System.out.println("I/O错误: " + e.getMessage());
            }
        }
    }

    private static class ClientHandler implements Runnable {
        private final SSLSocket clientSocket;

        public ClientHandler(SSLSocket socket) { this.clientSocket = socket; }

        public void run() {
            try {
                System.out.println("远程主机地址:‌" + this.clientSocket.getRemoteSocketAddress());
                while (true) {
                    ObjectInputStream in = new ObjectInputStream(this.clientSocket.getInputStream());
                    ObjectOutputStream out = new ObjectOutputStream(this.clientSocket.getOutputStream());
                    Map<?, ?> messageFromClient = InformationProcessing.decrypt((Map<?, ?>) in.readObject());
                    System.out.println("收到客户端信息: " + messageFromClient);
                    if ("Exit".equals(messageFromClient.get("msg"))) break;
                    else if ("GetHeartbeatInterval".equals(messageFromClient.get("msg"))) {
                        System.out.println("接收到来自" + this.clientSocket.getRemoteSocketAddress() + "的心跳间隔获取请求");
                        out.writeObject(InformationProcessing.encrypt(Map.of("response", 30)));
                    } else if ("".equals(messageFromClient.get("msg"))) {
                        System.out.println("接收到来自" + this.clientSocket.getRemoteSocketAddress() + "的心跳包");
                        out.writeObject(InformationProcessing.encrypt(Map.of("response", true)));    //回复客户端,‌表示已接收到心跳包
                    } else {
                        Map<?, ?> data = ((Map<?, ?>) messageFromClient.get("data"));
                        if ("SendVerificationCode".equals(messageFromClient.get("msg"))) {
                            System.out.println("向邮箱" + data.get("email") + "发送验证码");
                            out.writeObject(InformationProcessing.encrypt(Map.of("response", true)));
                            String verificationCode = VerificationCodeGenerator.generateVerificationCode(4);
                            System.out.println(verificationCode);
                        }
                    }
                }
            } catch (IOException | ClassNotFoundException e) {
                if (!e.getMessage().equals("Connection reset")) {
                    System.err.println("与客户端通信出错: ");
                    e.printStackTrace();
                }
            } finally {
                try {
                    this.clientSocket.close();
                    System.out.println("与" + clientSocket.getRemoteSocketAddress() + "的连接已断开");
                } catch (IOException e) {
                    System.err.println("关闭客户端连接时出错: ");
                    e.printStackTrace();
                }
            }
        }
    }
}

openssl测试端口

openssl s_client -connect localhost:8443
Connecting to 127.0.0.1
CONNECTED(000001F0)
Can't use SSL_get_servername
80710000:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:ssl\record\rec_layer_s3.c:907:SSL alert number 40
---
no peer certificate available
---
No client certificate CA names sent
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 243 bytes and written 299 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

服务端好像没有发送证书。
测试时服务端和客户端都运行在本地主机。
请问怎么解决这个问题?

  • 写回答

4条回答 默认 最新

  • L.W.Kevin0wvf 2024-09-16 15:04
    关注

    感谢各位的回答,目前已经解决问题。
    把程序加入tomcat服务器的webapps后配置好conf/server.xml文件即可正常使用TLS连接。
    但还需要改套接字为HTTP协议。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(3条)

报告相同问题?

问题事件

  • 系统已结题 9月24日
  • 已采纳回答 9月16日
  • 创建了问题 9月15日

悬赏问题

  • ¥15 如何在vue.config.js中读取到public文件夹下window.APP_CONFIG.API_BASE_URL的值
  • ¥50 浦育平台scratch图形化编程
  • ¥20 求这个的原理图 只要原理图
  • ¥15 vue2项目中,如何配置环境,可以在打完包之后修改请求的服务器地址
  • ¥20 微信的店铺小程序如何修改背景图
  • ¥15 UE5.1局部变量对蓝图不可见
  • ¥15 一共有五道问题关于整数幂的运算还有房间号码 还有网络密码的解答?(语言-python)
  • ¥20 sentry如何捕获上传Android ndk 崩溃
  • ¥15 在做logistic回归模型限制性立方条图时候,不能出完整图的困难
  • ¥15 G0系列单片机HAL库中景园gc9307液晶驱动芯片无法使用硬件SPI+DMA驱动,如何解决?