0
votes

I am creating a demo chat app with spring boot angular. I have implemented JWT authentication with spring security. I am able to authenticate successfully but fail to establish a successful WebSocket connection. It fails with error below:

stomp.js:134 - Opening Web Socket...
zone-evergreen.js:2863 - GET http://localhost:8090/chatal/info?t=1617996229738 40
stomp.js:134 Whoops! Lost connection to http://localhost:8090/chatal

This is very strange because In the WebSocket configuration I have enabled the allow origin.

registry.addEndpoint("/chatal").setAllowedOrigins("*").withSockJS();

Below are the configurations on the backend side:

  • The application security configuration with jwt
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/api/auth/**")
                .permitAll()
                .anyRequest()
                .authenticated();
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

  • Enabled CORS in WEB MVC configuration
@EnableWebMvc
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry){
        corsRegistry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .maxAge(3600L)
                .allowedHeaders("*")
                .exposedHeaders("Authorization","X-Auth-Token")
                .allowCredentials(true);
    }
}
  • WebSocket Configuration
@Configuration
@EnableWebSocketMessageBroker
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
@Slf4j
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

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

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic");
    }
}
  • WebSocket Security Configuration
@Configuration
public class WebSocketSecurityConfiguration extends AbstractSecurityWebSocketMessageBrokerConfigurer {

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

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}
  • A angular chat service responsible for connecting with backend:
import {Injectable, OnDestroy} from '@angular/core';
import {Client, Message, over} from "stompjs";
import {BehaviorSubject, Observable} from "rxjs";
import {SocketState} from "../model/SocketState";
import {environment} from "../../environments/environment";
import * as SockJS from "sockjs-client";
import {filter, first, switchMap} from "rxjs/operators";
import {StompSubscription} from "@stomp/stompjs";
import {LocalStorageService} from "./local-storage.service";
import {AppSettings} from "../app.settings";

@Injectable({
  providedIn: 'root'
})
export class ChatService implements OnDestroy {

  private client: Client;
  private state: BehaviorSubject<SocketState>;

  constructor(private _localStorageSvc:LocalStorageService) {
    let token = this._localStorageSvc.get(AppSettings.AUTH_RESPONSE,{}).authenticationToken;
    const customHeaders ={
      "Authorization": `${token}`
    };
    console.log(customHeaders);
    this.client = over(<WebSocket>new SockJS(environment.socketURL));
    this.state = new BehaviorSubject<SocketState>(SocketState.ATTEMPTING);
    this.client.connect(customHeaders, () => {
      this.state.next(SocketState.CONNECTED);
    });
  }

  private connect(): Observable<Client> {
    return new Observable<Client>(observer => {
      this.state.pipe(filter(state => state === SocketState.CONNECTED)).subscribe(() => {
        observer.next(this.client);
      })
    });
  }

  onMessage(topic: string, handler = ChatService.jsonHandler): Observable<any> {
    return this.connect().pipe(first(), switchMap(client => {
      return new Observable<any>(observer => {
        const subscription: StompSubscription = client.subscribe(topic, message => {
          observer.next(handler(message));
        });
        return () => client.unsubscribe(subscription.id);
      });
    }));
  }

  onPlainMessage(topic: string): Observable<string> {
    return this.onMessage(topic, ChatService.textHandler);
  }

  send(topic: string, payload: any): void {
    this.connect()
      .pipe<Client>(first())
      .subscribe(client => client.send(topic, {}, JSON.stringify(payload)));
  }

  static jsonHandler(message: Message): any {
    return JSON.parse(message.body);
  }

  static textHandler(message: Message): string {
    return message.body;
  }

  ngOnDestroy() {
    this.connect().pipe<Client>(first()).subscribe(client=> client.disconnect(null as any));
  }

}

  • After authentication, the user has redirected the chat component:
export class ChatRoomComponent implements OnInit {

  constructor(private _chatService:ChatService) { }

  ngOnInit(): void {
  }

}

I have tried with postman the following URI:

  • URL: http://localhost:8090/chatal
  • Method: GET
  • Authorization: Bearer Token ...
  • Result: Welcome to SockJS!

  • URL: http://localhost:8090/chatal/info?t=1617904524809
  • Method: GET
  • Authorization: Bearer Token ...
  • Result: {"entropy":1335940544,"origins":[":"],"cookie_needed":true,"websocket":true}