Search code examples
angularcorsspring-websocketspring-cloud-gateway

Spring cloud gateway + service with websockets + angular result in CORS error


I have recently added websocket configuration to my spring application, which is behind a Spring cloud gateway. There is also an angular app which communicates with my service through the gateway. The pronlem is that when i try co connect to it this is the error that i get in the mozilla: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8050/escrow-service/ws/info?t=1663539514537. (Reason: Multiple CORS header ‘Access-Control-Allow-Origin’ not allowed)

and this is in edge: Access to XMLHttpRequest at 'http://localhost:8050/some-service/ws/info?t=1663599778176' from origin 'http://localhost:4200' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:4200, http://localhost:4200', but only one is allowed.

When I try connecting directly to my app without going trough gateway, everything works as it should.

This is the code for the web-socket configuration:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/notification");
        config.setApplicationDestinationPrefixes("/product");
    }

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

this is the code handling the cors in the gateway:

private CorsWebFilter corsFilter() {
        /*
         CORS requests are managed only if headers Origin and Access-Control-Request-Method are available on OPTIONS requests
         (this filter is simply ignored in other cases).

         This filter can be used as a replacement for the @Cors annotation.
        */
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedOriginPatterns(Collections.singletonList("*"));
        config.addAllowedHeader(ORIGIN);
        config.addAllowedHeader(CONTENT_TYPE);
        config.addAllowedHeader(ACCEPT);
        config.addAllowedHeader(AUTHORIZATION);
        config.addAllowedHeader(CONNECTION);
        config.addAllowedHeader(ACCEPT_ENCODING);
        config.addAllowedHeader(USER_AGENT);
        config.addAllowedHeader(CONTENT_LENGTH);
        config.addAllowedHeader(HOST);
        config.addAllowedHeader("Postman-Token");
        config.addAllowedHeader(COOKIE);
        config.addAllowedMethod(GET);
        config.addAllowedMethod(PUT);
        config.addAllowedMethod(POST);
        config.addAllowedMethod(OPTIONS);
        config.addAllowedMethod(DELETE);
        config.addAllowedMethod(PATCH);
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }

And this is the angular code for calling the endpoints:

connect() {
        console.log("Initialize WebSocket Connection");
        const _this = this;

        let socket = new SockJS(this.appComponent.connectionUrl);
        let destinationUrl=this.appComponent.destinationUrl;
        _this.stompClient = Stomp.over(socket);
        _this.stompClient.connect({}, function (frame) {
            console.log('Connected: ' + frame);
            _this.stompClient.subscribe(destinationUrl, function (greeting: any) {
                let obj = JSON.parse(greeting.body);
                console.log(obj);
                _this.onMessageReceived(obj);
            });
        });
    };

Solution

  • After commenting with K.Nicholas, I found the solution and added it programatically as everywhere, there was only declarative solution.

    @Bean
        public RouteLocator applicationRouteLocator(RouteLocatorBuilder builder) {
            return builder.routes()
                    
                    .route(p -> p.path("/my-service/**")
                            .filters(gatewayFilterSpec -> gatewayFilterSpec
                                    .rewritePath("/escrow-service/(?<remaining>.*)", "/${remaining}")
                                    .dedupeResponseHeader("Access-Control-Allow-Origin","RETAIN_UNIQUE")
                                    .dedupeResponseHeader("Access-Control-Allow-Credentials","RETAIN_UNIQUE")
                                    .filter(customLoggingFilter))
                            .uri("lb://my-service/"))
                    .build();
        }
    

    the key was in adding

    .dedupeResponseHeader("Access-Control-Allow-Origin","RETAIN_UNIQUE")
    .dedupeResponseHeader("Access-Control-Allow-Credentials","RETAIN_UNIQUE")
    

    these headers so that the duplicates are removed.