Search code examples
springspring-bootspring-securitywebsocketspring-websocket

Send User-specific message with spring


I have an endpoint (/create) which has some logic and it takes 3-4 min to process so I used rabbitmq and as soon as the endpoint receive the request it takes the body and post the message in rabbitmq, the listener listens to the message and process the request now I want to notify the user that his request is successfully processed.

  • Is websocket correct choice for this requirement
  • Is there other better way through which i can achieve my goal?

So I went forward with websocket since I am using oauth based authentication I am unable to get web-socket work

Here is my code I have written:

SocketConfig.java

@Configuration
@EnableWebSocketMessageBroker
public class SocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic","/secured/queue");
    //config.setApplicationDestinationPrefixes("/app");
    //config.setUserDestinationPrefix("/secured/user");
  }

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/secured/messagereg").setAllowedOrigins("*").withSockJS();
  }

SocketHandler.java

@Configuration
public class SocketHandler extends AbstractSecurityWebSocketMessageBrokerConfigurer {

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

  @Override
  protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
        .simpDestMatchers("/secured/**", "/secured/**/**").authenticated()
        .anyMessage().authenticated();
  }
}

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Profile("!test")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private Auth0PropertyConfig config;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    JwtWebSecurityConfigurer
        .forRS256(config.getAudience(), config.getIssuer())
        .configure(http)
        .cors()
        .and()
        .csrf().disable()
        .authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    ;
  }
}

clientCode

 const socket = new SockJs("/secured/messagereg/?access_token="+token);
    this.setState({ clientRef: Stomp.over(socket) }, () =>
      this.state.clientRef.connect({},
        frame => {
          this.setState({ connection: true });
          this.state.clientRef.subscribe("/user/secured/queue/reply", message => {
            console.log("asd received ----------" + message.body);
            this.setState(prevs => ({
              message: [...prevs.message, message]
            }));
          });
        },
        error => {
          console.log("Stomp protocol error." + error);
        }
      )
    );

I am getting 401 unauthorized while connecting to socket.


Solution

  • In my opinion: a push messaging pattern (for example using STOMP) is suitable for this scenario, but that ultimately depends on your architectural principles. You could also poll the server for result (using REST API) which has both advantages (shared security architecture) and disadvantages (client code, traffic, and reaction-time overheads).

    Answer:

    In order to get your code working, I think you need one more method in your SocketConfig.java, which will hook into your OAUTH filter (or whatever method you may have in place).

    Important - websocket auth does not reuse existing Spring Security context. That's why you need to implement auth again, for example in the SocketConfig class using the WebSocketMessageBrokerConfigurer's method configureClientInboundChannel.

    The following example assumes you have already obtained the OAUTH token previously, and it's only used to reauthenticate the websocket connection. Setting the user reference in StompHeaderAccessor (3rd last line) will enable your code to send a message to the correct user.

    It also requires that the OAUTH token is set in the message header, as opposed to the endpoint parameter in your example. I think that may be safer for websocks messaging as the message itself is encrypted on protocol level if you use wss.

    @Autowired
    private YourOauthService auth;
    
    @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);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    String token = accessor.removeNativeHeader("Authorization").get(0);
                    Authentication user = auth.getAuthentication(token);
    
                    accessor.setUser(user);
                }
                return message;
            }
        });
    }
    

    I found some more interesting examples in https://robertleggett.wordpress.com/2015/05/27/websockets-with-spring-spring-security/