Search code examples
springspring-mvcwebsocketstompspring-websocket

Spring MVC Websockets with STOMP - Authenticate against specific channels


Is there a way in AbstractWebSocketMessageBrokerConfigurer (Spring Boot) to intercept the registration of users to a specific channel?

I have a basic authentication done in registerStompEndpoints using a HandshakeHandler:

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
   HandshakeHandler handler = new DefaultHandshakeHandler() {
      @Override
      protected Principal determineUser(ServerHttpRequest request, 
            WebSocketHandler wsHandler, Map<String, Object> attributes) {
       Principal principal = request.getPrincipal();
       if (principal == null) {
         return () -> getPrincipal();
       }
       return principal;
     }
   };
   registry.addEndpoint("/websocket")
    .setHandshakeHandler(handler)
    .setAllowedOrigins("*").withSockJS();
}

Now I would like to prevent this user from registering to '/topic/admin/news' if the user does not have the permission 'admin'. I'm not using Spring Security. I'd like to have an interceptor before the registration to a channel happens.

As an alternative, I'd like to use the SimpMessagingTemplate to only send out messages to users from the channel that have the permission. Is there a way to see what users are currently connected to my stomp-connection?


Solution

  • public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.setInterceptors(new TopicSubscriptionInterceptor());
    }
    

    And the interceptor:

    public class TopicSubscriptionInterceptor implements ChannelInterceptor {
    
    private static Logger logger = org.slf4j.LoggerFactory.getLogger(TopicSubscriptionInterceptor.class);
    
    @Override
    public Message<?> postReceive(Message<?> message, MessageChannel chanenel) {
        return message;
    }
    
    @Override
    public void postSend(Message<?> message, MessageChannel chanel, boolean sent) {
    }
    
    @Override
    public boolean preReceive(MessageChannel channel) {
        return true;
    }
    
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor headerAccessor= StompHeaderAccessor.wrap(message);
        if (StompCommand.SUBSCRIBE.equals(headerAccessor.getCommand()) && headerAccessor.getHeader("simpUser") !=null &&  headerAccessor.getHeader("simpUser") instanceof UsernamePasswordAuthenticationToken) {
            UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) headerAccessor.getHeader("simpUser");
            if(!validateSubscription((User)userToken.getPrincipal(), headerAccessor.getDestination()))
            {
                throw new IllegalArgumentException("No permission for this topic");
            }
        }
        return message;
    }
    
    private boolean validateSubscription(User principal, String topicDestination)
    {
        logger.debug("Validate subscription for {} to topic {}",principal.getUsername(),topicDestination);
        //Validation logic coming here
        return true;
    }
    }