I'm working on creating a user-to-user chat application using Spring and React, and I want to prevent anonymous users from connecting to the WebSocket. Additionally, I'm using OAuth2 Resource Server for client authentication with JWT tokens. My security configuration is implemented as follows:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(requests -> requests
.requestMatchers("/ws/**").permitAll()
.requestMatchers("/api/**").permitAll()
.requestMatchers("/secret/user").hasAuthority(USER)
.requestMatchers("/secret/admin").hasAuthority(ADMIN)
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
.build();
}
Here is the WebSocket-related implementations:
public class WebAuthHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
// Auto rejecting for testing purpose
boolean isAuthenticated = false;
if (isAuthenticated) {
return true;
} else {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getBody().write("Authentication failed".getBytes(StandardCharsets.UTF_8));
return false; // Reject the WebSocket handshake
}
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
// TODO Auto-generated method stub
}
}
@Configuration
@EnableWebSocketMessageBroker
@CrossOrigin(origins = "${allowedCrossOrigin}")
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Value("${allowedCrossOrigin}")
private String allowedCrossOrigin;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins(allowedCrossOrigin)
.addInterceptors(new WebAuthHandshakeInterceptor())
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
}
I have to permit all requests to the WebSocket endpoint; otherwise, it never reaches the custom HandshakeHandler to perform authentication. I didn't find any other way to send the client's token to the web server.
React connection to the endpoint:
const socket = new SockJS("http://localhost:7777/backend/ws");
There is no problem when the authentication succeeds, but when it gets rejected, problems start to show. The following pile of errors appears after the beforeHandshake returns false: Thrown errors by react.
Is there any way to terminate the SockJS gracefully after the authentication fails?