You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Spring Boot WebSocket客户端支持SSL/TLS连接?

WebSocket(WS)转WSS启用SSL后连接失败,报错"Unable to create SSLEngine to support SSL/TLS connections"

问题描述

代码在WS(无加密)环境下运行正常,但启用SSL切换为WSS后无法连接,报错信息:

Unable to create SSLEngine to support SSL/TLS connections

场景:互联网消息先到webhook,再转发至WebSocket,启用SSL后Java客户端无法正常工作。

正常运行的WS代码(EX1)

private static WebSocketSessionHandler CLIENTONESESSIONHANDLER = new WebSocketSessionHandler();

public StompSession stompConnection() {
    StompSession session = null;
    try {
        StandardWebSocketClient client = new StandardWebSocketClient();
        WebSocketStompClient stompClient = new WebSocketStompClient(client);
        stompClient.setMessageConverter(new MappingJackson2MessageConverter());
        CompletableFuture<StompSession> sessionAsync = stompClient
                .connectAsync("ws://localhost:8433/websocket-server", CLIENTONESESSIONHANDLER);

        session = sessionAsync.get();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    return session;
}

public void sendMessageToWebSocket(GenericMessageDto obj) {
    StompSession session = stompConnection();
    session.subscribe("/topic/message", CLIENTONESESSIONHANDLER);
    session.send("/app/process-message", obj);
}

public class WebSocketSessionHandler extends StompSessionHandlerAdapter {
    @Override
    public Type getPayloadType(StompHeaders headers) {
        return OutgoingMessage.class;
    }

    @Override
    public void handleFrame(StompHeaders headers, Object payload) {
        System.out.println("Received: " + ((OutgoingMessage) payload).getContent());
    }
}

报错的WSS代码(EX2)

private static WebSocketSessionHandler CLIENTONESESSIONHANDLER = new WebSocketSessionHandler();

public StompSession stompConnection() {
    StompSession session = null;
    try {
        String websocketUrl = "wss://localhost:8443/websocket-server";

        StandardWebSocketClient client = new StandardWebSocketClient();

        Map<String, Object> userProperties = new HashMap<>();
        userProperties.put("org.apache.tomcat.websocket.SSL_CONTEXT", SSLContext.getDefault());
        userProperties.put("org.apache.tomcat.websocket.SSL_PROTOCOLS", SSLContext.getDefault());
        userProperties.put("org.apache.tomcat.websocket.SSL_TRUSTSTORE", SSLContext.getDefault());
        client.setUserProperties(userProperties);
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        SSLContext sslContext = SSLContext.getDefault();
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        WebSocketStompClient stompClient = new WebSocketStompClient(client);
        stompClient.setMessageConverter(new MappingJackson2MessageConverter());

        session = stompClient.connectAsync(websocketUrl, CLIENTONESESSIONHANDLER, sslSocketFactory, container, sslContext).get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return session;
}

public void sendMessageToWebSocket(GenericMessageDto dto) {
    StompSession session = stompConnection();
    session.subscribe("/topic/message", CLIENTONESESSIONHANDLER);
    session.send("/app/process-message", dto);
}

问题分析

  1. 属性配置错误:EX2中错误地将SSLContext对象赋值给SSL_PROTOCOLS和SSL_TRUSTSTORE属性,这两个属性的合法值分别是协议字符串/字符串数组(如"TLSv1.2")和信任库路径字符串,传入SSLContext对象会导致SSLEngine初始化失败。
  2. 重载方法误用:connectAsync的参数传递错误,StandardWebSocketClient已通过userProperties配置SSL上下文,无需额外传入SSLSocketFactory、WebSocketContainer等参数。
  3. 证书信任问题:如果使用自签名证书,默认SSLContext不会信任该证书,需自定义TrustManager处理。

修正后的WSS代码

通用场景(CA签发证书)

private static WebSocketSessionHandler CLIENTONESESSIONHANDLER = new WebSocketSessionHandler();

public StompSession stompConnection() {
    StompSession session = null;
    try {
        String websocketUrl = "wss://localhost:8443/websocket-server";

        // 初始化默认SSLContext(适用于CA签发的可信证书)
        SSLContext sslContext = SSLContext.getDefault();

        StandardWebSocketClient client = new StandardWebSocketClient();
        Map<String, Object> userProperties = new HashMap<>();
        // 设置SSL上下文
        userProperties.put("org.apache.tomcat.websocket.SSL_CONTEXT", sslContext);
        // 指定SSL协议,按需调整
        userProperties.put("org.apache.tomcat.websocket.SSL_PROTOCOLS", "TLSv1.2,TLSv1.3");
        client.setUserProperties(userProperties);

        WebSocketStompClient stompClient = new WebSocketStompClient(client);
        stompClient.setMessageConverter(new MappingJackson2MessageConverter());

        // 使用正确的connectAsync重载方法
        CompletableFuture<StompSession> sessionAsync = stompClient.connectAsync(websocketUrl, CLIENTONESESSIONHANDLER);
        session = sessionAsync.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return session;
}

public void sendMessageToWebSocket(GenericMessageDto dto) {
    StompSession session = stompConnection();
    session.subscribe("/topic/message", CLIENTONESESSIONHANDLER);
    session.send("/app/process-message", dto);
}

public class WebSocketSessionHandler extends StompSessionHandlerAdapter {
    @Override
    public Type getPayloadType(StompHeaders headers) {
        return OutgoingMessage.class;
    }

    @Override
    public void handleFrame(StompHeaders headers, Object payload) {
        System.out.println("Received: " + ((OutgoingMessage) payload).getContent());
    }
}

自签名证书场景(仅测试环境使用)

如果使用自签名证书,需自定义TrustManager信任该证书:

// 自定义TrustManager,信任所有证书(测试环境专用,生产环境禁止)
private static SSLContext createTrustAllSSLContext() throws Exception {
    TrustManager[] trustAllCerts = new TrustManager[]{
        new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {}
            public void checkServerTrusted(X509Certificate[] certs, String authType) {}
        }
    };
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustAllCerts, new SecureRandom());
    return sslContext;
}

然后在stompConnection方法中替换SSLContext初始化:

// 替换原SSLContext.getDefault()为自定义方法
SSLContext sslContext = createTrustAllSSLContext();

内容的提问来源于stack exchange,提问作者Johnes Camargo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:15:02