iuhettiarachchi
iuhettiarachchi

Reputation: 451

Unable to acquire WebSocket connection with Spring Cloud Gateway and Spring Security

I'm developing an application which require WebSocket connections. I have developed a Spring Boot microservice called notification-service which holds web socket configurations. Further, I have exposed all the notification-service APIs via gateway-service (exposed via port 8747) with integrated with Spring Security.

I have the following code to test the WebSocket connection.

<html>
<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js"></script>
    <script type="text/javascript">
            var stompClient = null;
            const LOCAL_URL = 'http://localhost:8747/websocket/notification-service/ws';

            var socket = new SockJS(LOCAL_URL);
            
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function(frame) {
                console.log(frame);
                stompClient.subscribe('/topic/notification', function(result) {
                    show(JSON.parse(result.body));
                });
            });

            function sendMessage() {
                var text = document.getElementById('text').value;
                stompClient.send("/app/application", {},
                  JSON.stringify({'text':text}));
            }
            
            function show(message) {
                var response = document.getElementById('messages');
                var p = document.createElement('p');
                p.innerHTML= "message: "  + message.message;
                response.appendChild(p);
            }
        </script>
</head>
<body>
<div>
    <div>
        <button id="sendMessage" onclick="sendMessage();">Send</button>
        <input type="text" id="text" placeholder="Text"/>
    </div>
    <br />
    <div>
        <button id="sendPrivateMessage" onclick="sendPrivateMessage();">Send Private</button>
        <input type="text" id="privateText" placeholder="Private Message"/>
        <input type="text" id="to" placeholder="To"/>
    </div>
    <br />
    <br />
    <br />

        <div id="messages"></div>

</div>

</body>
</html>

Please also find following, Spring Security configurations and response filter which I have configured in gateway-service. I have whitelisted all endpoints related to the web sockets as well.

Spring Security configuration

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    private static final String[] AUTH_WHITE_LIST = {
        "/websocket/sds-notification-service/**"
    };

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
            .csrf(ServerHttpSecurity.CsrfSpec::disable)
            .authorizeExchange(exchanges -> exchanges.pathMatchers(AUTH_WHITE_LIST)
                    .permitAll()
                    .anyExchange().authenticated())
            .build();
    }
}

Post Filter

@Configuration
public class GlobalResponseFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            var response = exchange.getResponse();
            var request = exchange.getRequest();
            response.getHeaders().set("Access-Control-Allow-Credentials", "true");
            response.getHeaders().set("Access-Control-Allow-Origin", request.getHeaders().getOrigin());
            exchange.mutate().response(response).build();
        }));
    }

    @Override
    public int getOrder() {
        return 0;
    }

}

I'm unable to establish the WebSocket connection as I'm getting the following error in the console.

enter image description here

Update

I do have another authorization filter (which I forgot to mentioned earlier) as for the following code to perform some permission validation. And I remove that filter, I was able to connect to the WebSocket as expected.

@Component
public class AuthorizationFilter extends AbstractGatewayFilterFactory<AuthorizationFilter.Config> {

    public AuthorizationFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> ReactiveSecurityContextHolder.getContext()
            .map(securityContext -> {
                if (someValidateLogic()) {
                    return chain.filter(exchange);
                } else {
                    return Mono.error(new AuthorizationServiceException(HttpStatus.FORBIDDEN.toString()));
                }
            })
            .flatMap(classMono -> classMono)
            .then());
    }
}

I'm not sure whether this due to a invalid filter order which we have configured.

Upvotes: 2

Views: 61

Answers (0)

Related Questions