Search code examples
javassl-certificatestompspring-websocket

How to make spring boot websocket client works with SSL/TLS?


My code works perfectly with WS: connection, but it doesn't work when I include my SSL certificate. Let me explain the cenario. When someone sends a message over the internet it reaches my webhook and my webhook sends that message to my websocket. Unfortunately, when I use SSL my java code doesn't work with SSl. The best approach I had was this messa "Unable to create SSLEngine to support SSL/TLS connections" EX2.

EX1 - This code works fine with WS/http

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());
    }

}

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);
    }

Solution

  • I've found a solution. Fisrt of all you need to add the trust store file and key store file. Have a look here: https://www.baeldung.com/spring-boot-https-self-signed-certificate

    Load the keystore using a special class Keystore as exampl below. When using SSl we need to get the instace: SSLContext sslContext = SSLContext.getInstance("TLS");

    Just remember to change this ""wss://:5000/websocket-server" for your website url. It is not going to work if you write down "localhost" nor your IP address because SSL. I wrote a method as a connection and returning a session and then I do the subcribe like this:

    public void updateWebsocket() {
        StompSession session = stompConnectionTLS();
        session.subscribe("/topic/messages", CLIENT_SESSION_HANDLER);
        session.send("/app/process", true);
        session.disconnect();
    }
    
    
    public StompSession stompConnectionTLS() {
    
        StompSession session = null;
    
        String websocketUrl = "wss://<yourURL>:5000/websocket-server";
    
        try {
    
            String trustStoreFile = "/Path/cert.jks";
            String trustStorePassword = "Password";
            String keyStoreFile = "/Path/keystore.p12";
            String keyStorePassword = "Password";
    
            // Load the trust store
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(new FileInputStream(trustStoreFile), trustStorePassword.toCharArray());
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);
    
            // Load the key store
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());
    
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
    
            StandardWebSocketClient webSocketClient = new StandardWebSocketClient();
            webSocketClient.setUserProperties(Collections.singletonMap("org.apache.tomcat.websocket.SSL_CONTEXT", sslContext));
    
            WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
            stompClient.setMessageConverter(new MappingJackson2MessageConverter());
    
            session = stompClient.connectAsync(websocketUrl, CLIENTSESSIONHANDLER).get();
    
        } catch (Exception e) {
            e.printStackTrace();
    
        }
    
        return session;
    }