Securing WebSockets

So far, we have secured the chat service and the images service.

Or have we?

Well, we configured chat as the Gateway API for our microservices using Spring Cloud Gateway. To do that, we made it the sole source of HTTP session creation. Given that the session details were also included in forwarded web requests, our Gateway API is nicely buttoned up.

However,  the chat microservice's critical function is brokering WebSocket messages. And we haven't lifted a finger to secure that component. Time to roll up our sleeves and get to work.

Since our WebSocket handlers are stream oriented, we merely need to slip in a parent class that authorizes things when the WebSocket session is configured, as follows:

    abstract class AuthorizedWebSocketHandler 
     implements WebSocketHandler { 
 
       @Override 
       public final Mono<Void> handle(WebSocketSession session) { 
         return session.getHandshakeInfo().getPrincipal() 
          .filter(this::isAuthorized) 
          .then(doHandle(session)); 
       } 
 
       private boolean isAuthorized(Principal principal) { 
         Authentication authentication = (Authentication) principal; 
         return authentication.isAuthenticated() && 
          authentication.getAuthorities().contains("ROLE_USER"); 
       } 
 
       abstract protected Mono<Void> doHandle( 
         WebSocketSession session); 
    } 

The preceding code can be described as follows:

  • This abstract class implements the WebSocketHandler interface with a Reactor-based handle() function
  • The handle method looks up handshakeInfo, finding the Principal that will be populated by Spring Security, and filters against a custom isAuthorized function
  • If the session is indeed authorized, an abstract doHandle is invoked, handing over WebSocketSession to the actual handlers
  • The isAuthorized function takes the session's Principal, casts it to a Spring Security Authentication, and verifies that the user is both authenticated and also contains ROLE_USER

With this in place, we can update our InboundChatService like this:

    @Service 
    @EnableBinding(ChatServiceStreams.class) 
    public class InboundChatService extends AuthorizedWebSocketHandler
{ private final ChatServiceStreams chatServiceStreams; public InboundChatService(ChatServiceStreams chatServiceStreams){ this.chatServiceStreams = chatServiceStreams; } @Override protected Mono<Void> doHandle(WebSocketSession session) { ... }

The changes in the previous code can be described as follows:

  • InboundChatService now extends AuthorizedWebSocketHandler, forcing it to accept those upstream checks
  • We have replaced handle(WebSocketSession) with doHandle(WebSocketSession)
  • The rest of the code is the same, so there's no reason to show it

If we apply the same changes to OutboundChatService and CommentService, we can ensure that all of our WebSocket services are locked down.

Admittedly, our policy is quite simple. However, we can easily scale based on requirements. For example, if the Admins wanted their own channel, it wouldn't be hard to add /topic/admin/** and require ROLE_ADMIN.

It's also important to recognize that this level of security is aimed at the whole channel. Adding per-message security checks could also be layered in by going to each concrete service, and, essentially, embedding .filter(), based on the details of the message.

And that's all it takes! Our WebSocket channels are now secured such that only proper incoming messages are allowed through, and only HTML served from our site will have the means to connect and send such messages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.51.157