springmulti-tenantspring-websocket

spring multi tenant application with websockets?


I have a working spring multi tenant application. It uses the familiar construct of a thread local variable and a web filter, and uses the hostname (subdomain) to determine which tenant is active.

But the browser also opens a websocket and sends messages. Somehow I need to do the same trick there; I need to match an incoming message to a tenant, aka the hostname that was used to start the websocket. There is no concept of a filter for websockets.

There is a HandshakeInterceptor in which the hostname can be accessed, or maybe via a WebSocketEventListener, so something probably can be constructed there. But I cannot seem to find a way link an incoming message (in a @MessageMapping method of a @Controller class) to anything sensible.

Any insights in how to do that?


Solution

  • It took some digging, but for people ending up with the same question. Put the code that determines the tenantId from a HttpServletRequest and the code that configures any logging (e.g. MDC) already present in de TenantFilter in reusable static methods. And then:

    1. Determine and store the tenantId in the HandshakeInterceptor
    .addInterceptors(new HandshakeInterceptor() {
        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
    
            // Determine the tenantId (see TenantFilter)
            ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
            HttpServletRequest httpServletRequest = servletServerHttpRequest.getServletRequest();
            String tenantId = TenantFilter.determineTenantId(httpServletRequest, ...);
            attributes.put("tenantId", tenantId);
            return true;
        }
    
        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        }
    })
    
    
    1. In the message processing methods in the WebsocketController class use the Header annotation to get the attributes and get the tenantId from that:
    @MessageMapping("/SetNoteworthy")
    public void browserToBackend(SetNoteworthy command, @Header("simpSessionId") String sessionId, @Header("simpSessionAttributes") Map<String, String> attributes) throws Exception {
        String tenantId = attributes.get("tenantId");
    
        if (LOG.isInfoEnabled()) LOG.info(BROWSER_TO_BACKEND + command);
        TenantFilter.setCurrentTenant(tenantId, ...);
    }