Problems while establishing conection using stomp and SockJS with anonymous and registered users

200 Views Asked by At

I'm trying to establish a connection with back with Stomp (Spring Boot) using SockJS (Angular). I have registered users and anonymous ones. The anonymous ones are going to use the WebSocket connection, but while connecting I have an error.

While trying to connect:

>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000

I receive the next answer:

<<< ERROR
message:Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException\c Access denied content-length:0

For Angular I'm making the connection using the following code:

private stompClient: Client = over(new SockJS(`http://localhost:8080/socket`));

  constructor() {}

  public connect (callback: Function): void {
    this.stompClient.connect({}, frame => {
      console.log(frame)
      const sessionId = frame?.headers;  // Here you get the Session ID
      console.log(sessionId);
      callback(this.stompClient)
    }, (frame) => {console.error(frame)})
  }

  public sendMessage(endpoint: string, message: string): void {
    this.stompClient.send(endpoint, {}, message)
  }

  public disconnet(callback: Function): void {
    this.stompClient.disconnect(callback());
  }

For Spring Boot Stomp connection I have the following:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/socket")
                .setAllowedOrigins("*")
                .withSockJS();
    }
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app")
                .enableSimpleBroker("/message", "/passport");
    }

    @Override
    public void configureWebSocketTransport( WebSocketTransportRegistration registration )
    {
        registration.setMessageSizeLimit( 300000 * 50 );
        registration.setSendTimeLimit( 30 * 10000 );
        registration.setSendBufferSizeLimit( 3 * 512 * 1024 );
    }

}
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
                .authorizeRequests()
                .antMatchers("/socket/**")
                .permitAll()
                // other endpoints
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        httpSecurity.addFilterBefore(jwtAuthenticationFilter,
                UsernamePasswordAuthenticationFilter.class);

        httpSecurity.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

        authenticationManagerBuilder.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());

    }

}
@AllArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtProvider jwtProvider;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        try {
            if (shouldSkipJwtFilter(request)) {
                filterChain.doFilter(request, response);
                return;
            }
            String jwt = getJwtFromRequest(request);
            if (StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)) {

                String username = jwtProvider.getUsernameFromJwt(jwt);
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);

            }

            filterChain.doFilter(request, response);
        } catch (ExpiredJwtException e) {
            if(request.getRequestURL().toString().contains("/refresh/token")) {
                allowForRefreshToken();
                filterChain.doFilter(request, response);
            } else {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
    }
    private boolean shouldSkipJwtFilter(HttpServletRequest request) {
        return request.getRequestURI().startsWith("/socket");
    }

    private String getJwtFromRequest(HttpServletRequest request) {

        String bearerToken = request.getHeader("Authorization");

        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {

            return bearerToken.substring(7);

        }

        return bearerToken;

    }

    private void allowForRefreshToken() {
        UsernamePasswordAuthenticationToken authentication;
        authentication = new UsernamePasswordAuthenticationToken(null, null, null);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}
@Component
public class CorsFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, @NotNull FilterChain filterChain)
            throws ServletException, IOException {
        response.setHeader("Access-Control-Allow-Origin", "http://localhost:4200");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");
        response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            filterChain.doFilter(request, response);
        }
    }

}
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

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

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

Things that I tried:

  • Before adding sessionCreationPolicy(SessionCreationPolicy.STATELESS);, I had a registered user that was able to connect and use the websocket, but I don't want them to be registered as this is just for a group chat.
  • I also tried using a HandshakeInterceptor for the Stomp, but I'm not sure I implemented it correctly as it made it impossible for the users to reach the endpoint.
0

There are 0 best solutions below