Maintain Spring Session during WebSocket Connection

Maintain Spring Session during WebSocket Connection thumbnail
123K
By Dhiraj 28 August, 2019

In this post, we will be discussing about how to maintain spring session during websocket connection through HandshakeInterceptor. Doing so we can track user session during every websocket request and can utilize this session to track client activities from the server or can be used to provide an extra level of security even after the server is connected through websocket protocol. Now let us look into the code how can we maintain spring session during websocket connection in Spring boot.

The question comes why we sometimes require to maintain session during a websocket connection. Actually, we can't send any HTTP headers using stompClient, because STOMP is layer over websockets, and only when websockets handshake happen we have possibility to send custom headers. Hence, if we require to add any extra validations or security checks over the data sent from the client to the server then this session could be useful.

Here, we won't be discussing about the authentication and authorization for websocket connections because with Spring 4 WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made. To ensure a user has authenticated to your WebSocket application, all that is necessary is to ensure that you setup Spring Security to authenticate your HTTP based web application.

To add authentication to your websocket connection, you can visit this nice explanation on spring official doc.

How Session is Maintained

As we know, before making a websocket connection to a server, client makes a handshake request as an upgrade request and this request is a HTTP request. Hence, to maintain a websocket session, we require to intercept this HTTP request and keep the session id somewhere from where it can be accessed everytime whenever a websocket request is made. Here we will be using STOMP header attributes to track the session.

Environment Setup

1. JDK 8 2. Spring Boot 3. Intellij Idea/ eclipse 4. Maven

Maven Dependencies

There is no any extra maven dependency is required for this case that we used in our previous post of Spring Websocket Integration Example without STOMP. Hence let us ignore it for time being.

Server Side

Spring Boot Websocket Configuration

First of all let us configure our message broker. In WebSocketConfig.java we have defined our STOMP endpoint and message broker. The important thing to notice here is the configuration for handshakeInterceptor.So, whenever any websocket handshake request is received by the server,this class will come into picture.

The endpoint /greeting is the endpoint that client will be using to start with the handshake and that configuration is done below.

During this handshake itself, we can put any extra attribute in the session object that we require for any future use case.

WebSocketConfig.java
package com.developertack;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
	
	 @Override
	 public void configureMessageBroker(MessageBrokerRegistry config) {
	  config.enableSimpleBroker("/topic/", "/queue/");
	  config.setApplicationDestinationPrefixes("/app");
	 }
	 
	 @Override
	 public void registerStompEndpoints(StompEndpointRegistry registry) {
	  registry.addEndpoint("/greeting").addInterceptors(new HttpHandshakeInterceptor());
	 }
	
}
 Other Interesting Posts

Spring Boot Websocket Interceptor

Now let us define the httpInterceptor. We have overriden beforeHandshake() to intercept the handshake request and set our custom attribute in the session. We are setting our custom attribute sessionId with actual sessionId and this is the same key which we will be using later to track the session from our controller class.

You can use this method to store any other attribute of your wish.

HttpHandshakeInterceptor.java
package com.developertack;

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

public class HttpHandshakeInterceptor implements HandshakeInterceptor {

	@Override
	public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Map attributes) throws Exception {
		if (request instanceof ServletServerHttpRequest) {
			ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
			HttpSession session = servletRequest.getServletRequest().getSession();
			attributes.put("sessionId", session.getId());
		}
		return true;
	}

	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Exception ex) {
	}
}

Websocket Subscription Event Listener

Following is the listener class which will be executed whenever an event is raised when a new WebSocket client using a Simple Messaging Protocol (STOMP) sends a subscription request for any queue or topic. We are also validating the sessionId which we created during handshake.

SubscribeEventListener.java
package com.developertack;

import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;

@Component
public class SubscribeEventListener implements ApplicationListener {

	@Override
	public void onApplicationEvent(SessionSubscribeEvent sessionSubscribeEvent) {
		StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(sessionSubscribeEvent.getMessage());
		System.out.println(headerAccessor.getSessionAttributes().get("sessionId").toString());
	}
}

Websocket Request Mapping

Now let us define the controller which will intercept the message coming from the stomp client and validate the session using custom attribute sessionId which we set during handshake.This attribute can be accessed from the headerAccessor.

package com.developertack.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;

@Controller
public class WebSocketController {
	
	@Autowired
	private SimpMessageSendingOperations messagingTemplate;
	
	@MessageMapping("/message")
    public void processMessageFromClient(@Payload String message, SimpMessageHeaderAccessor  headerAccessor) throws Exception {
		String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
		System.out.println(sessionId);
		headerAccessor.setSessionId(sessionId);
		messagingTemplate.convertAndSend("/topic/reply", new Gson().fromJson(message, Map.class).get("name"));
      
    }

}

Client Side Implementation

At the client side, we have a .html file to trigger the websocket connection over a button click. The text entered in the text box will be sent to server over websocket protocol and the response from the server will be diaplayed in the Greetings div element.

Index.html
<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="/jquery-1.10.2.min.js"></script>
    <script src="/app.js"></script>
    <script src="/stomp.js"></script>
    
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
 
</div>
</body>
</html>

Now let us define our app.js. In app.js, we have connect() method that is triggered on the button click. The connection is requested at /greeting endpoint. This is the same endpoint that we configured as STOMP endpoint in our server side websocket configuration.

We also have the configuration to subscribe at /topic/reply and whenever a new message is recieved, the showGreeting() method will be invoked.


var ws;
function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
	//connect to stomp where stomp endpoint is exposed
	var socket = new WebSocket("ws://localhost:8080/greeting");
	ws = Stomp.over(socket);

	//usernaeme and password
	ws.connect({}, function(frame){
	
	  ws.subscribe("/topic/reply", function(msg) {
	    alert(msg);
	    showGreeting(msg.body);
	  });

	 setConnected(true);
	});
}

function disconnect() {
    if (ws != null) {
        ws.close();
    }
    setConnected(false);
    console.log("Disconnected");
}

function sendName() {
	var data = JSON.stringify({'name': $("#name").val()})
	 ws.send("/app/message", {},data);
}

function showGreeting(message) {
    $("#greetings").append(" " + message + "");
}

$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() { sendName(); });
});


Run Application

1. Run Application.java as a java application.

2. Open the browser and hit the URL - http:localhost:8080.

3. Click on connect button to make the socket connection.

4. Enter your name and click send.Following is the result.

web-socket-example

On the server side you can check the console and find the sessionIds are same.

Conclusion

I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section. The source code can be downloaded from te github here.

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on sprint-boot