Spring Boot WebSockets: Socket.io + Authentication + Postman

Ratnesh Mishra - Sep 9 - - Dev Community

Hello folks!! We all know HTTP requests just work fine, they might not be optimal for real-time interactions. This is where WebSockets come in. They offer fast and more efficient way to communicate. Ready to level up your skills? Let's jump into the tutorial and explore how to use WebSockets in Spring Boot using Socket.io alongwith Authentication.

Prerequisites

Before diving in, make sure you have the following things in place:

  1. Java 17+
  2. Maven: We'll be using Maven as our build and dependency management tool
  3. IDE: Choose any IDE you're comfortable with like IntelliJ IDEA or Eclipse.

Also, I presume you have basic understanding of WebSockets and Socket.IO to follow along smoothly. If you are new to these concepts, you can checkout the following resources:

Note: I have uploaded the complete code for the demonstration in this GitHub repository.

Setting Up The Project

  1. Open your IDE, create a new Maven project and configure the project's GroupID, ArtificatID and other details.
  2. Now open the pom.xml file and add the following dependencies under the <dependencies>...</dependencies> tag.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.corundumstudio.socketio</groupId>
        <artifactId>netty-socketio</artifactId>
        <version>2.0.11</version>
    </dependency>
    
    <dependency>
        <groupId>io.socket</groupId>
        <artifactId>socket.io-client</artifactId>
        <version>2.1.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.34</version>
        <scope>provided</scope>
    </dependency>
    
    
  3. Just save the pom.xml file, reload Maven and it will automatically download the required dependencies.

Creating WebSocket Configuration

  1. Create a WebSocket configuration class, for example, SocketIOConfig. Add the following properties to it:

    package com.ratnesh.demowebsocketapplication.config;
    
    import com.corundumstudio.socketio.SocketIOServer;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Component
    @RequiredArgsConstructor
    @Slf4j
    public class SocketIOConfig {
    
        // I have set the configuration values in application.yaml file
        @Value("${socket.host}")
        private String socketHost;
        @Value("${socket.port}")
        private int socketPort;
    
        // SocketIOServer class is used to create a socket server
        private SocketIOServer server;
    
    }
    
    
  2. You can configure your application.yaml file like this:

    socket:
      host: localhost
      port: 8081
    
  3. Now that we have setup the basic structure of the config class, we will add a bean method under the same class that will initialize the SocketIO server instance, setting it up with specified host and port, enabling bidirectional connections to our application.

    @Bean
    public SocketIOServer socketIOServer() {
        // Configuration object holds the server settings
        Configuration config = new Configuration();
    
        config.setHostname(socketHost);
        config.setPort(socketPort);
    
        server = new SocketIOServer(config);
        server.start();
    
        server.addConnectListener(client -> log.info("Client connected: {}", client.getSessionId()));
        server.addDisconnectListener(client -> log.info("Client disconnected: {}", client.getSessionId()));
    
        return server;
    } 
    
  4. We will add just one more method in the same class right before we end up setting the configuration and that is:

    @PreDestroy
    public void stopSocketServer() {
        this.server.stop();
    }
    
  5. So what above piece of code does is, whenever there is a call to shut down the Spring Container, @PreDestroy annotation calls the method to which it is annotated. As a result, it will ensure to close the SocketIO server properly before shutting down the application, implementing a better clean-up mechanism.

  6. At this point, we can test if we were able to configure our application correctly through postman. Now fire-up your postman, go to New -> Socket.IO and this will open up an untitled Socket.IO request. Enter your URL as localhost:8081 and hit Connect.
    Tada!! You're now connected to your socket server which you can check in your server logs as well.

Image showing how to connect to WebSocket through Postman

Adding Controller

Now, let's create the controller which will actually manage the connection and events. We can configure one or more controllers depending upon the requirements. To configure a controller you can follow the steps:

  1. Create a class, for example, WebSocketController, which is going to manage our socket events. Add the following properties and methods as below:

    package com.ratnesh.demowebsocketapplication.controller;
    
    import com.corundumstudio.socketio.AckRequest;
    import com.corundumstudio.socketio.SocketIOClient;
    import com.corundumstudio.socketio.SocketIOServer;
    import com.corundumstudio.socketio.listener.DataListener;
    import com.ratnesh.demowebsocketapplication.model.SocketDetail;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    @Component
    @Slf4j
    public class WebSocketController {
    
        protected final SocketIOServer socketServer;
    
        public WebSocketController(SocketIOServer socketServer) {
            this.socketServer = socketServer;
            this.socketServer.addEventListener("demoEvent", SocketDetail.class, demoEvent);
        }
    
        public DataListener<SocketDetail> demoEvent = new DataListener<>() {
            @Override
            public void onData(SocketIOClient client, SocketDetail socketDetail, AckRequest ackRequest) {
                log.info("Demo event received: {}", socketDetail);
                // Add your business logic here.
                ackRequest.sendAckData("Demo event received");
            }
        };
    }
    
    
  2. Here, we have a demoEvent method which represents an event and it returns an instance of type DataListener<SocketDetail> where SocketDetail is a model class containing a single field called name. Then we are adding this event listener to our socketServer instance through the constructor method.

    package com.ratnesh.demowebsocketapplication.model;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class SocketDetail {
        private String name;
    }
    
  3. You can add up your business logic as per your requirements and add more event listeners to the class.

Testing the Controller

Okay, so before we start authenticating our WebSocket connections with Spring Security, let's check out to see if our controller works fine up until this point. To do this, start your server and open your Postman.

  1. Connect to the server using the host and port specified in your configuration, e.g. localhost and 8081.
  2. Once you are connected with it, select JSON from the message type drop down and pass the body as shown in the image.
  3. Write the name of the event in the Event Name field and select the Ack option. Image showing how to connect and invoke events through Postman
  4. After hitting Send, you'll be getting an acknowledgement from the server that your request was received successfully. You are now successfully connected to the socket server and can communicate bidirectionally.

Authenticating WebSocket Connections

NOTE: I won't be going into full depth on how to authenticate using JWTs. Instead, I'll be using dummy values with a lose approach to authenticate users.

  1. You can set an Authorization Listener through the config in the SocketIOConfig class object which can be used to authenticate the connections like this:

    @Bean
    public SocketIOServer socketIOServer() {
        // Configuration object holds the server settings
        Configuration config = new Configuration();
    
        config.setHostname(socketHost);
        config.setPort(socketPort);
    
        // Authorization listener
        config.setAuthorizationListener(data -> {
            String token = data.getHttpHeaders().get("User-Name");
            if (!token.isEmpty()) {
                // You can extract user information from token using your JWTTokenUtil class and validate it
                // or throw error if token is invalid
                // you can pass more information in headers like role, email, etc.
                // data object can be used to add custom headers after authorization
                data.getHttpHeaders().add("User", "userDetailsAfterAuthorization");
                return new AuthorizationResult(true);
            }
            return new AuthorizationResult(false);
        });
    
        server = new SocketIOServer(config);
        server.start();
    
        server.addConnectListener(client -> log.info("Client connected: {}", client.getSessionId()));
        server.addDisconnectListener(client -> log.info("Client disconnected: {}", client.getSessionId()));
    
        return server;
    }
    
  2. This setup allows you to access the headers and retrieve user information during WebSocket communication. For example, if we need to access the headers in the WebSocketController class that we created, we can modify our demoEvent to something like this:

    public DataListener<SocketDetail> demoEvent = new DataListener<>() {
        @Override
        public void onData(SocketIOClient client, SocketDetail socketDetail, AckRequest ackRequest) {
            log.info("Demo event received: {}", socketDetail);
            String userInfo = client.getHandshakeData().getHttpHeaders().get("User-Name");
            log.info("User info: {}", userInfo);
            // Access user information added after authorization
            log.info(client.getHandshakeData().getHttpHeaders().get("User"));
            // Add your business logic here.
            ackRequest.sendAckData("Demo event received");
        }
    };
    
  3. And we are done with our authentication of socket connection and it's time for a show case now.

Testing WebSocket Authentication

  1. Add the required headers based on your configuration and connect to the WebSocket server.

    Image showing how to pass headers in a WebSocket request through Postmand

  2. Now, if we hit the same event again that we created earlier, i.e, demoEvent, we will see a log message that it is able to pickup the username that we provided in the User-Name request header.

Conclusion

Congratulations! You've successfully set up WebSocket communication with Socker.IO in your Spring Boot application. You've learned how to configure WebSocket settings, create and handle events and implementation basic authentication for your WebSocket connections.
I hope it was helpful for everyone reading this!! If you have any queries, feel free to drop them down in the comments or reach me out.

Thanks a lot for reading this!!

.
Terabox Video Player