1

I'm trying to implement Spring Websocket with simple broker, SockJS and STOMP. My application handles authentication in a custom interceptor (called SecurityInterceptor) which extends the HandlerInterceptorAdapter class. I want the HTTP requests made by the SOMP client when establishing the connection to go through my interceptor class so that I validate the user is an authenticated user. But these initial HTTP request don’t pass through my custom interceptor. Could someone enlighten me on this issue please? The following is what I’m doing

Main web config classs:

    @Configuration
    @EnableWebMvc
    @ComponentScan( { "..." } )
    public class RestWebSvcMainConfig extends WebMvcConfigurerAdapter {

        public void addInterceptors( final InterceptorRegistry oRegistry ) {

            try {

                oRegistry.addInterceptor( securityInterceptor() ).addPathPatterns( "/dms/secured/**" );
            }
            catch ( Exception ex ) {

                throw new RuntimeException( ex );
            }
        }

        @Bean
        public SecurityInterceptor securityInterceptor() throws Exception {
            SecurityInterceptor oSecurityInterceptor = new SecurityInterceptor( authenticationProvider() );
            return oSecurityInterceptor;
        }

    }

WebSocket config class:

@Configuration
@ComponentScan({"..."})
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/dms/secured/hello").withSockJS();
    }

}

Controller class:

@Controller
public class GreetingController {
    @MessageMapping("/dms/secured/hello")
    @SendToUser(value="/topic/greetings", broadcast = false)
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(3000); // simulated delay
        return new Greeting("Hello, " + message.getName() + "!");
    }

}

Dispatcher servlet mapping in web.xml:

<!-- Map all requests to the dispatcher servlet -->
 <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
 </servlet-mapping>

STOMP client:

function connect() {
  var socket = new SockJS('/GlobalAPI/dms/secured/hello');
  stompClient = Stomp.over(socket);  
  stompClient.connect({}, function(frame) {
     setConnected(true);
     console.log('Connected: ' + frame);
     stompClient.subscribe('/user/topic/greetings', function(greeting){
        showGreeting(JSON.parse(greeting.body).content);
     });
  });
}

GlobalAPI is the context root.

The initial request which is http://localhost:8080/GlobalAPI/dms/secured/hello/info does not go through the interceptor. When I make any other HTTP requests like http://localhost:8080/GlobalAPI/dms/secured/documents, the request go thorough the interceptor nicely. What's wrong with my code?

JavaDev_007
  • 51
  • 2
  • 7
  • Why should it. The request isn't processed by a handler and as such the `HandlerInterceptor` doesn't apply. For intercepting web socket connections you want to use a `HandshakeInterceptor` probably. – M. Deinum Jul 01 '15 at 13:52

1 Answers1

0

I recommend you implement your own implementation of HandshakeInterceptor. I copy all the code of one implementation of Spring framework:

package mx.config.ws;

import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

public class HttpSessionHandshakeInterceptor_personalised implements HandshakeInterceptor {


    /**
     * The name of the attribute under which the HTTP session id is exposed when
     * {@link #setCopyHttpSessionId(boolean) copyHttpSessionId} is "true".
     */
    public static final String HTTP_SESSION_ID_ATTR_NAME = "HTTP.SESSION.ID";


    private final Collection<String> attributeNames;

    private boolean copyAllAttributes;

    private boolean copyHttpSessionId = true;

    private boolean createSession;


    /**
     * Default constructor for copying all HTTP session attributes and the HTTP
     * session id.
     * @see #setCopyAllAttributes
     * @see #setCopyHttpSessionId
     */
    public HttpSessionHandshakeInterceptor_personalised() {
        this.attributeNames = Collections.emptyList();
        this.copyAllAttributes = true;
    }

    /**
     * Constructor for copying specific HTTP session attributes and the HTTP
     * session id.
     * @param attributeNames session attributes to copy
     * @see #setCopyAllAttributes
     * @see #setCopyHttpSessionId
     */
    public HttpSessionHandshakeInterceptor_personalised(Collection<String> attributeNames) {
        this.attributeNames = Collections.unmodifiableCollection(attributeNames);
        this.copyAllAttributes = false;
    }


    /**
     * Return the configured attribute names to copy (read-only).
     */
    public Collection<String> getAttributeNames() {
        return this.attributeNames;
    }

    /**
     * Whether to copy all attributes from the HTTP session. If set to "true",
     * any explicitly configured attribute names are ignored.
     * <p>By default this is set to either "true" or "false" depending on which
     * constructor was used (default or with attribute names respectively).
     * @param copyAllAttributes whether to copy all attributes
     */
    public void setCopyAllAttributes(boolean copyAllAttributes) {
        this.copyAllAttributes = copyAllAttributes;
    }

    /**
     * Whether to copy all HTTP session attributes.
     */
    public boolean isCopyAllAttributes() {
        return this.copyAllAttributes;
    }

    /**
     * Whether the HTTP session id should be copied to the handshake attributes
     * under the key {@link #HTTP_SESSION_ID_ATTR_NAME}.
     * <p>By default this is "true".
     * @param copyHttpSessionId whether to copy the HTTP session id.
     */
    public void setCopyHttpSessionId(boolean copyHttpSessionId) {
        this.copyHttpSessionId = copyHttpSessionId;
    }

    /**
     * Whether to copy the HTTP session id to the handshake attributes.
     */
    public boolean isCopyHttpSessionId() {
        return this.copyHttpSessionId;
    }

    /**
     * Whether to allow the HTTP session to be created while accessing it.
     * <p>By default set to {@code false}.
     * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
     */
    public void setCreateSession(boolean createSession) {
        this.createSession = createSession;
    }

    /**
     * Whether the HTTP session is allowed to be created.
     */
    public boolean isCreateSession() {
        return this.createSession;
    }


    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
            WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {


        // Set ip attribute to WebSocket session
        attributes.put("ip", request.getRemoteAddress());

        // ============================================= CODIGO PERSONAL
        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
        HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
//        httpServletRequest.getCookies();
//        httpServletRequest.getParameter("inquiryId");
//        httpServletRequest.getRemoteUser();



        System.out.println("SessionHandshakeInterceptor::beforeHandshake()    httpServletRequest.getRemoteUser()(): "+httpServletRequest.getRemoteUser());

        //  Salida:     "/127.0.0.1:8080"
        System.out.println("SessionHandshakeInterceptor::beforeHandshake()    request.getLocalAddress(): "+request.getLocalAddress());
        //  Salida:     "http://localhost:8080/ServLocalBancarias-0.0.1-SNAPSHOT/chat/923/uezxomjg/websocket"
        System.out.println("SessionHandshakeInterceptor::beforeHandshake()    request.getURI(): "+request.getURI());
        //  Salida:     "/127.0.0.1:59197"
        System.out.println("SessionHandshakeInterceptor::beforeHandshake()    request.getRemoteAddress(): "+request.getRemoteAddress());
        //  Salida: null
        System.out.println("SessionHandshakeInterceptor::beforeHandshake()    request.getPrincipal(): "+request.getPrincipal());
        /*
         *   {
               Origin=[http://localhost:3000],
               Cookie=[__utma=111872281.293045146.1470257629.1470257649.1470362764.31;
               __utmc=111872281;
               __utmz=111872281.1470257629.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)],
               Sec-WebSocket-Key=[XGDdYK2TDz6djy852SzBNg==],
               User-Agent=[Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36],
               Connection=[Upgrade],
               Sec-WebSocket-Version=[13],
               Host=[localhost:8080],
               Accept-Encoding=[gzip, deflate, sdch],
               DNT=[1],
               Pragma=[no-cache],
               Upgrade=[websocket],
               Sec-WebSocket-Extensions=[permessage-deflate;
               client_max_window_bits],
               Cache-Control=[no-cache],
               Accept-Language=[en-US,en;q=0.8]
             }

         */
        System.out.println("SessionHandshakeInterceptor::beforeHandshake()    request.getHeaders(): "+request.getHeaders());

        /*
         * Salida:
         */
        attributes.forEach((key,value)->{System.out.println("SessionHandshakeInterceptor::beforeHandshake()   attributes Key: "+key+" Value: "+value);});








        // ============================================== CODIGO ORIGINAL DE Spring INI
        HttpSession session = getSession(request);
        if (session != null) {

            // ===================================== INI

             System.out.println("SessionHandshakeInterceptor::beforeHandshake()    session: "+session);


            // ===================================== END


            if (isCopyHttpSessionId()) {
                attributes.put(HTTP_SESSION_ID_ATTR_NAME, session.getId());
            }
            Enumeration<String> names = session.getAttributeNames();
            while (names.hasMoreElements()) {
                String name = names.nextElement();
                if (isCopyAllAttributes() || getAttributeNames().contains(name)) {
                    attributes.put(name, session.getAttribute(name));
                }
            }
        }
        // ============================================== CODIGO ORIGINAL DE Spring END



        return true;
    }

    private HttpSession getSession(ServerHttpRequest request) {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
            return serverRequest.getServletRequest().getSession(isCreateSession());
        }
        return null;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
            WebSocketHandler wsHandler, Exception ex) {

         System.out.println("SessionHandshakeInterceptor::afterHandshake()");

    }


}

Next, you need create a bean:

@EnableScheduling
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Bean
    public HttpSessionHandshakeInterceptor_personalised handShakeInterceptor() {
        return new HttpSessionHandshakeInterceptor_personalised();
    }

Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //registry.addEndpoint("/ws").withSockJS();

        // Enpoint para el chat de pruebas.
        registry.addEndpoint("/chat")
//          .addInterceptors(new IpHandshakeInterceptor())
//          .addInterceptors(new SessionHandshakeInterceptor())// FUNCIONA TAMBIEN, IGUAL QUE EL OTRO
//          .addInterceptors(new HttpSessionHandshakeInterceptor())
            .addInterceptors(handShakeInterceptor()) // Este el mejor y unico necesario.
//          .setHandshakeHandler(handshakeHandler)
            .setAllowedOrigins("*")     // ELIMINAR. USADO PARA PERMITIR CONECTARSE DESDE OTRO SERVIDOR TIPO LOCALHOST
            .withSockJS()
            ;       


    }
    ... 
}

This work for me.

Sergio
  • 441
  • 9
  • 22