Spring-cloud-gateway: Cors Pre Flight Request

Created on 15 Feb 2019  路  29Comments  路  Source: spring-cloud/spring-cloud-gateway

Spring Cloud Version: Greenwich.RELEASE

To make my application work with CORS, I used the global cors in the yaml file as the following

spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/*]':
allowedOrigins: '
'
allowedMethods:
- ''
allowedHeaders: '
'

This actually works well when there is no preflight request. However, if I make a call with an Authorization header (which will make the browser make a pre-flight request), this is not enough to make it work. I actually need to add a route like that:

routes:

  • id: options-S3-call
    uri: someuirhere
    predicates:
    - Method=OPTIONS

I dont quite understand why this works, but shouldnt the globacors do the trick by itself? Moreover, the uri in the options route is simply irrelevant, but seems you need to have a route that matches the OPTIONS method for the pre-flight call in one of your routes, otherwise it won't work.

What I get is a 403 status and the following message:
"Access to fetch at 'http://localhost:8080/api/webcontent/en/menu' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled."

bug

Most helpful comment

To version pre to 2.1.3.RELEASE, this works for me:

@Configuration
public class PreFlightCorsConfiguration {

    @Bean
    public CorsWebFilter corsFilter() {
        return new CorsWebFilter(corsConfigurationSource());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
        config.addAllowedMethod(HttpMethod.PUT);
        config.addAllowedMethod(HttpMethod.DELETE);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

All 29 comments

Thanks for the reminder

implements WebFluxConfigurer

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowCredentials(true)
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("*")
                .exposedHeaders(HttpHeaders.SET_COOKIE);
    }

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addExposedHeader(HttpHeaders.SET_COOKIE);
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsWebFilter(corsConfigurationSource);
    }

Hello,

we also discovered this problem in our project.
Since the fix is planned to be released with version 2.1.3, I was looking for a way to use it right now.
We currently use 2.1.1.RELEASE.
Is there any version, milestone may be we could use right now?

thanks
Dmitri

@mitok you can use Greenwich.BUILD-SNAPSHOT

i tried the fix with BUILD-SNAPSHOT version and did not get it run.

this may because of my configuration.
I configured gateway RouteLocator bean and cors like this:

@Configuration
@EnableWebFlux
public class CorsConfig implements WebFluxConfigurer {

@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/*")
.allowedOrigins("http://ifap.vos:4200")
.allowedHeaders("
")
.allowedMethods("*");
// .maxAge(3600);
}
also put this value into configuration.
spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true

This is what i get on start..


APPLICATION FAILED TO START


Description:

Field simpleUrlHandlerMapping in org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration required a bean of type 'org.springframework.web.reactive.handler.SimpleUrlHandlerMapping' that could not be found.

The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)

Action:

Consider defining a bean of type 'org.springframework.web.reactive.handler.SimpleUrlHandlerMapping' in your configuration.

I'm not very experienced in WebFlux and gateway. What could be wrong here?

thanks for help
regards
Dmitri

i configure this bean.
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
return simpleUrlHandlerMapping;
}

but still get:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

It seems that it does not work with RouteLocator or i do something wrong..

You should only have to set the property spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true. You should not need to create the SimpleUrlHandlerMapping bean since it should be provided by webflux.
I suggest you create a github project which demonstrates the issue you are seeing.

To version pre to 2.1.3.RELEASE, this works for me:

@Configuration
public class PreFlightCorsConfiguration {

    @Bean
    public CorsWebFilter corsFilter() {
        return new CorsWebFilter(corsConfigurationSource());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
        config.addAllowedMethod(HttpMethod.PUT);
        config.addAllowedMethod(HttpMethod.DELETE);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

You should only have to set the property spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true. You should not need to create the SimpleUrlHandlerMapping bean since it should be provided by webflux.
I suggest you create a github project which demonstrates the issue you are seeing.

i will create a small sample.

To version pre to 2.1.3.RELEASE, this works for me:

@Configuration
public class PreFlightCorsConfiguration {

    @Bean
    public CorsWebFilter corsFilter() {
        return new CorsWebFilter(corsConfigurationSource());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
        config.addAllowedMethod(HttpMethod.PUT);
        config.addAllowedMethod(HttpMethod.DELETE);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

Unfortunately we can not use WebFilter. We have several Spring Boot Services embedded in one Big Spring Boot Service. So WebFilter is called twice in our configuration. Why and if we could avoid it is other story. If it called twice, the cors header is placed also twice in response and this does not like browser.

I just run into the same problem. I use version Greenwich.BUILD-SNAPSHOT and set property spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true to enable the latest fix. The project started successfully, but the problem remains the same: preflight requests (with method "OPTIONS") are not handled.

To version pre to 2.1.3.RELEASE, this works for me:

@Configuration
public class PreFlightCorsConfiguration {

    @Bean
    public CorsWebFilter corsFilter() {
        return new CorsWebFilter(corsConfigurationSource());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
        config.addAllowedMethod(HttpMethod.PUT);
        config.addAllowedMethod(HttpMethod.DELETE);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

this works for me! thx!

Hi @fifman

Which versions do you use?
I'm using Greenwich.SR2(-->spring-cloud-starter-gateway 2.1.2.RELEASE)
plus the Filter class that works for you.

But, I'm getting this error message:
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.

Hi @fifman

Which versions do you use?
I'm using Greenwich.SR2(-->spring-cloud-starter-gateway 2.1.2.RELEASE)
plus the Filter class that works for you.

But, I'm getting this error message:
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.

Could you double check your configuration? Looks like corsFilter applies twice in your filter chain. I suggest you put a break point at the line of return new CorsWebFilter(corsConfigurationSource()); to see how many times it stops right there and where it comes from.

I did double check.
Duplication has a reason with spring cloud gateway,

I found a workaround here:
https://blog.csdn.net/zimou5581/article/details/90043178

Thanks a lot to this human.

This workaround does work and I suggest it be included as an option for a future release of Spring Cloud. Many thanks to Zimou5581 and kcotzen.

Baeldung describes the use of this type of Global, Ordered filter in
https://www.baeldung.com/spring-cloud-custom-gateway-filters

For anyone interested, I have made a slight modification to the workaround posted by Zimou5581. I already had a bean for the CORS config in SecurityConfig.java file.

@Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); configuration.setAllowedMethods(Arrays.asList("GET","POST", "PUT", "DELETE", "PATCH", "OPTIONS")); configuration.setExposedHeaders(Arrays.asList("Authorization", "content-type")); configuration.setAllowedHeaders(Arrays.asList("Authorization", "content-type")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }

I then just created another file for the CorsResponseHeaderFilter and copied/pasted the code from Zimou5581.

`@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {

@Override
public int getOrder() {
    // NettyWriteResponseFilter
    return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    return chain.filter(exchange).then(Mono.defer(() -> {
        exchange.getResponse().getHeaders().entrySet().stream()
                .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
                .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
                        || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)))
                .forEach(kv -> {
                    kv.setValue(new ArrayList<String>() {{
                        add(kv.getValue().get(0));
                    }});
                });

        return chain.filter(exchange);
    }));
}

}`

Everything worked as intended after that.

There's already a DedupeResponseHeader filter.

Spencer - thanks for the reply. Before implementing the workaround, I added the following to the application.yml file:

spring: cloud: gateway: default-filters: - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE

Nothing changed so I sought the work around. Is there something in the yml that is incorrect? Also, is there a reference for this dedupe filter? I cannot find any links regarding this topic.

Any help is greatly appreciated. Best.

the unformatted yaml looks weird so I can't say

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-deduperesponseheader-gatewayfilter-factory

Sorry about the bad yaml... I pasted into code section and that was result. Perhaps this is clearer:

spring:
cloud:
gateway:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE

From the link you sent me, it appears that the DedupeResponseHeader filter needs to be inserted as a filter for each route and does not work as a global default-filter.

One more small question - is the strategy parameter separated from the response header cases with a comma?

Again, many thanks for the help. It is greatly appreciated.

no, just the examples show per filter, yes, separated by comma. If you have anymore questions please find us on gitter or stack overflow rather than a closed issue.

To version pre to 2.1.3.RELEASE, this works for me:

@Configuration
public class PreFlightCorsConfiguration {

    @Bean
    public CorsWebFilter corsFilter() {
        return new CorsWebFilter(corsConfigurationSource());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
        config.addAllowedMethod(HttpMethod.PUT);
        config.addAllowedMethod(HttpMethod.DELETE);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

this works for me! thx!

For me too ! With Springboot 2.2.5.RELEASE, Spring cloud Hoxton.SR3, and Spring cloud security 2.2.1.RELEASE ! HUGE THANKS!!! :)

the unformatted yaml looks weird so I can't say

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-deduperesponseheader-gatewayfilter-factory

it works for me ,Thanks a lot!!!!
the dependency of my gateway is org.springframework.cloud.spring-cloud-gateway.2.2.2.RELEASE

the unformatted yaml looks weird so I can't say
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-deduperesponseheader-gatewayfilter-factory

it works for me ,Thanks a lot!!!!
the dependency of my gateway is org.springframework.cloud.spring-cloud-gateway.2.2.2.RELEASE

What configuration options did you try? I tried different combinations and it still doesn't work.

I showed the yaml I tried above although it did not work. IMHO - there is a problem with the built in Spring Gateway DeDupe filter. It may be with the documentation but I could not get it to work. I think others are experiencing the same. Implementing a filter directly with using the yaml for the built in filter works great. There are a few similar examples in this post. I suggest that you try these (it's basically copying and pasting into your app; you may have to add a new class to hold the bean, filter, etc.)

I showed the yaml I tried above although it did not work. IMHO - there is a problem with the built in Spring Gateway DeDupe filter. It may be with the documentation but I could not get it to work. I think others are experiencing the same. Implementing a filter directly with using the yaml for the built in filter works great. There are a few similar examples in this post. I suggest that you try these (it's basically copying and pasting into your app; you may have to add a new class to hold the bean, filter, etc.)

I was actually asking @Wanxp since according to their last comment their got it working. But yeah, still trying to get this thing to work.

To version pre to 2.1.3.RELEASE, this works for me:

@Configuration
public class PreFlightCorsConfiguration {

    @Bean
    public CorsWebFilter corsFilter() {
        return new CorsWebFilter(corsConfigurationSource());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
        config.addAllowedMethod(HttpMethod.PUT);
        config.addAllowedMethod(HttpMethod.DELETE);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

This worked for me thank you so much @hank-cp ...

I had same cors issue for preflight Options request with spring cloud gateway. it started working after put PreFlightCorsConfiguration class in and configuration in yml
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
corsConfigurations:
'[/*]':
allowedOrigins: "
"
allowedMethods:
- GET
- POST
- DELETE
- PUT
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

i am using 2.3.3.RELEASE spring boot, Hoxton.SR7 which is 2.2.4 for spring cloud

Was this page helpful?
0 / 5 - 0 ratings