I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we’ll describe how to use Spring WebSockets to send STOMP messages to a single user. That’s important because we sometimes don’t want to broadcast every message to every user. Besides that, we’ll demonstrate how to send these messages in a secure way.

For an introduction to WebSockets, check out this great tutorial for how to get up and running. And, for a deeper dive into security, check out this article to secure your WebSockets implementation.

2. Queues, Topics, and Endpoints

There are three main ways to say where messages are sent and how they are subscribed to using Spring WebSockets and STOMP:

  1. Topics – common conversations or chat topics open to any client or user
  2. Queues – reserved for specific users and their current sessions
  3. Endpoints – generic endpoints

Now, let’s take a quick look at an example context path for each:

  • “/topic/movies”
  • “/user/queue/specific-user”
  • “/secured/chat”

It’s important to note that we must use queues to send messages to specific users, as topics and endpoints don’t support this functionality.

3. Configuration

Now, let’s learn how to configure our application so that we can send messages to a specific user:

public class SocketBrokerConfig extends 
  AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/secured/user/queue/specific-user");
        config.setApplicationDestinationPrefixes("/spring-security-mvc-socket");
        config.setUserDestinationPrefix("/secured/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/secured/room").withSockJS();
    }
}

Let’s make sure to include a user destination since that determines which endpoints are reserved for single users.

We also prefix all of our queues and user destinations with “/secured” to make them require authentication. For unprotected endpoints, we can drop the “/secured” prefix (as a result of our other security settings).

From a pom.xml standpoint, no additional dependencies are required.

4. URL Mappings

We want our client to subscribe to a queue using a URL mapping that conforms to the following pattern:

"/user/queue/updates"

This mapping will be automatically transformed by UserDestinationMessageHandler into the user-session-specific address.

For example, if we have a user named “user123”, the corresponding address would be:

"/queue/updates-user123"

Server-side, we’ll send our user-specific response using the following URL mapping pattern:

"/user/{username}/queue/updates"

This too will be transformed into the correct URL mapping we already subscribed to client-side.

Thus, we see that the essential ingredients here are two-fold:

  1. Prepend our specified User Destination Prefix (configured in AbstractWebSocketMessageBrokerConfigurer).
  2. Use “/queue” somewhere within the mapping.

In the next section, we’ll take a look at exactly how to do this.

5. Invoking convertAndSendToUser()

We can non-statically invoke convertAndSendToUser()  from SimpMessagingTemplate or SimpMessageSendingOperations:

@Autowired
private SimpMessagingTemplate simpMessagingTemplate;

@MessageMapping("/secured/room") 
public void sendSpecific(
  @Payload Message msg, 
  Principal user, 
  @Header("simpSessionId") String sessionId) throws Exception { 
    OutputMessage out = new OutputMessage(
      msg.getFrom(), 
      msg.getText(),
      new SimpleDateFormat("HH:mm").format(new Date())); 
    simpMessagingTemplate.convertAndSendToUser(
      msg.getTo(), "/secured/user/queue/specific-user", out); 
}

You might have noticed:

@Header("simpSessionId") String sessionId

The @Header annotation allows access to headers exposed by the inbound message. For example, we can grab the current sessionId without the need for complicated interceptors. Similarly, we can access the current user via Principal.

Importantly, the approach we take in this article provides greater customization over the @sendToUser annotation with respect to URL mappings. For more on that annotation, check out this great article.

Client-side, we’ll use connect() in JavaScript to initialize a SockJS instance and connect to our WebSocket server using STOMP:

var socket = new SockJS('/secured/room'); 
var stompClient = Stomp.over(socket);
var sessionId = "";

stompClient.connect({}, function (frame) {
    var url = stompClient.ws._transport.url;
    url = url.replace(
      "ws://localhost:8080/spring-security-mvc-socket/secured/room/",  "");
    url = url.replace("/websocket", "");
    url = url.replace(/^[0-9]+\//, "");
    console.log("Your current session is: " + url);
    sessionId = url;
}

We also access the supplied sessionId and append that to the “secured/room  URL mapping. This gives us the ability to dynamically and manually supply a user-specific subscription queue:

stompClient.subscribe('secured/user/queue/specific-user' 
  + '-user' + that.sessionId, function (msgOut) {
     //handle messages
}

Once everything’s set up we should see:

 

And in our server console:

6. Conclusion

Check out the official Spring blog and the official documentation for more information about this topic.

As always, the code samples used in this article is available over on GitHub.

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE LESSONS

newest oldest most voted
Notify of
Mike
Guest
Mike

Can you tell me why you are using the session ID on the url when you subscribe in the Javascript, I assumed Spring+Spring security was able to figure out which User to reply to with convertAndSendToUser()?