如何让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); }
问题分析
- 属性配置错误:EX2中错误地将
SSLContext对象赋值给SSL_PROTOCOLS和SSL_TRUSTSTORE属性,这两个属性的合法值分别是协议字符串/字符串数组(如"TLSv1.2")和信任库路径字符串,传入SSLContext对象会导致SSLEngine初始化失败。 - 重载方法误用:
connectAsync的参数传递错误,StandardWebSocketClient已通过userProperties配置SSL上下文,无需额外传入SSLSocketFactory、WebSocketContainer等参数。 - 证书信任问题:如果使用自签名证书,默认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
相关产品推荐
相关产品推荐

