为什么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)
---
服务端好像没有发送证书。
测试时服务端和客户端都运行在本地主机。
请问怎么解决这个问题?