Search code examples
springwebsocketjwtchatstomp

How to authenticate websocket chat by JWT


I have a functional project of chat by websockets that is already working, now I need to implement authentication by JWT. This is the flow of the requests

  1. Client connects to ws://localhost:port/chatting
  2. Client subscribes to its own inbox, in order to receive messages from other clients: /user/{userId}/queue/chatting
  3. Client can send messages to other users in /app/message by specifying in body: { 'message': msg, 'from': "userId", 'to': "recipientId" }, and the system will redirect them to /user/{recipientId}/queue/chatting

I would like to secure:

  1. A user has to authenticate in order to do the handshake
  2. A user has to be "userId" in order to subscribe to /user/"userId"/queue/chatting, so that other users can't access it.
  3. A user has to be "userId" in order to send a message to another user with "userId" in from inside body

Solution

  • First of all adding authentication can be done by configuring the same in WebSecurityConfig , adding a custom JwtRequestFilter which is going to parse you JWT token and set the authentication context for that request.

    A good reference for the same: create-apis-with-jwt-authorization-using-spring-boot.

    Regarding 2 and 3, Springboot does not allow exposing dynamic endpoints for STOMP registration, so you will have to expose /user/queue/chatting and clients will have to directly subscribe to this. However, using convertAndSendToUser(userId, destination, payload) you can send the message based on the userId. This function internally calls this function which does this this.destinationPrefix(/user) + user(/username) + destination(/queue/chatting) so as you can see the variable user is getting prefixed to final destination, you can pass userId instead of userName.

    Reference for this: simpMessagingTemplate.convertAndSendToUser

    But do note in this case you would have to set the username for this session as userId itself. This can be done in your custom JwtRequestFilter

    private void populateSecurityContextHolder(HttpServletRequest request, String userId, List<SimpleGrantedAuthority> grantedAuthorities) {
            PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(
                    userId, null, grantedAuthorities);
            authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authToken);
            log.info("Authentication set for userId " + userId);
        }
    

    So with this, your server can send messages based on userId it receives in the messages.