Spring Boot 2.6.x (and Spring Security) + Websocket + Angular 13 (rx-stomp)

1k Views Asked by At

my application is structured as follows:

Spring Boot

WebSocket Config

@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.interceptors(new ChannelInterceptor() {

        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {
            StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
            
            Map<String, Object> authUserInfo = new HashMap<>();
            if (accessor.getHeader("simpUser") != null && BearerTokenAuthentication.class.isAssignableFrom(accessor.getHeader("simpUser").getClass())) {
                BearerTokenAuthentication auth = (BearerTokenAuthentication) accessor.getHeader("simpUser");
                authUserInfo = auth.getTokenAttributes();
            }
            
            if (accessor.getNativeHeader("Authorization") != null) {
                String token = accessor.getFirstNativeHeader("Authorization");
                authUserInfo.put("Authorization", token);
            }
            
            log.info("WebSocket - Msg type: {}. User Info: {}", accessor.getCommand(), authUserInfo);
            return message;
        }

    });
    
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket-endpoint").setAllowedOriginPatterns("*");
        registry.addEndpoint("/websocket-endpoint").setAllowedOriginPatterns("*").withSockJS();
}

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

Security Config

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages.anyMessage().authenticated();
    }

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

...

@Configuration
@Order(2)
public static class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.oauth2.resourceserver.introspection-uri}")
    private String introspectionUri;
    @Value("${spring.security.oauth2.resourceserver.client-id}")
    private String clientId;
    @Value("${spring.security.oauth2.resourceserver.client-secret}")
    private String clientSecret;

    @Bean
    public OpaqueTokenIntrospector introspector() {
        return new NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http //
                .requestMatchers().antMatchers("/api/**", "/websocket-endpoint/**", "/websocket-endpoint", "/messages/**).and() //
                .authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated()) //                  .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken).cors().and() //
                .csrf().disable();
    }
}

What I want is every websocket message (CONNECT, SUBSCRIBE, etc ...) to be authenticated via my oauth2 configuration. This works with my Java test client:

public static void main(String[] args) {

    if (args == null || args.length < 1) {
        throw new RuntimeException();
    }
    
    WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
    headers.add("Authorization", "Bearer " + args[0]);
    
    WebSocketClient client = new StandardWebSocketClient();
    WebSocketStompClient stompClient = new WebSocketStompClient(client);
    stompClient.setMessageConverter(new MappingJackson2MessageConverter());
    StompSessionHandler sessionHandler = new MyStompSessionHandler();
    stompClient.connect("ws://localhost:7081/websocket-endpoint", headers, sessionHandler);
    new Scanner(System.in).nextLine();
}

public static class MyStompSessionHandler extends StompSessionHandlerAdapter {
        
        @Override
        public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
            log.info("New session established : " + session.getSessionId());
            session.subscribe("/messages/messages-in-a-bottle", this);
            log.info("Subscribed");
        }

        @Override
        public void handleTransportError(StompSession session, Throwable exception) {
            log.error("Got an exception", exception);
        }
        
        @Override
        public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
            log.error("Got an exception", exception);
        }

        @Override
        public Type getPayloadType(StompHeaders headers) {
            return MyResource.class;
        }

        @Override
        public void handleFrame(StompHeaders headers, Object payload) {
            MyResource msg = (MyResource) payload;
            log.info("Received : " + msg);
        }
    }

but i can't get it to work with Angular and rx-stomp. Could someone show me a guide on how to set up a connect / subscribe on Angular (with access-token Oauth2)? I also tried with SockJS and STOMP but couldn't

0

There are 0 best solutions below