Configure CORS policy for Spring Cloud Gateway
Asked Answered
F

11

5

I have Spring Cloud gateway running on separate server with the following configuration:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/*]':   (I also tried '[/**]':)
            allowedOrigins: "http://localhost:3000"
            allowedMethods:
              - GET
              - POST

But every time from React app I get:

Access to XMLHttpRequest at 'http://11.1.1.1:8080/api/support/tickets/create' 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.

Do you know how this issue an be solved for Spring Boot 2.6.2/Spring cloud 2021.0.0?

Full code: http://www.github.com/rcbandit111/Spring_Cloud_Gateway_POC

POST Request:

Request URL: http://1.1.1.1:8080/api/merchants/onboarding/bank_details
Referrer Policy: strict-origin-when-cross-origin
Accept: application/json, text/plain, */*
Content-Type: application/json
Referer: http://localhost:3000/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36

Post request Payload:
{"currency":"HNL","iban":"dssvsdvsdv"}

OPTIONS request:

Request URL: http://1.1.1.1:8080/api/merchants/onboarding/bank_details
Request Method: OPTIONS
Status Code: 403 Forbidden
Remote Address: 1.1.1.1:8080
Referrer Policy: strict-origin-when-cross-origin

Response:

HTTP/1.1 403 Forbidden
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
content-length: 0

OPTIONS Request headers:

OPTIONS /api/merchants/onboarding/bank_details HTTP/1.1
Host: 1.1.1.1:8080
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
Sec-Fetch-Mode: cors
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Foreandaft answered 3/1, 2022 at 3:58 Comment(8)
Have you checked allowedOrigins's value is correct ?Tetrachloride
What does the request look like?Uel
If you're hoping to get an answer to your question, you should add details about the kind of request that your frontend sends to http://11.1.1.1:8080/api/support/tickets/create, including its HTTP method and headers.Uel
I added request data from Chrome's console.Foreandaft
Do you need this specific for the Spring Boot 2.6.2/Spring cloud 2021.0.0 Version or in general?Peltast
I need this in general for latest stable release. But you can show me several cases.Foreandaft
I add my solution for an older Version below as answer.Peltast
Any idea is it going to work for latest version?Foreandaft
C
5

Are you running your FE app via --proxy-conf to redirect? I am not a well versed in FE, but dont use changeOrigin: true. If you have to to use changeOrigin: true, it will only work for GET and for others you might have to do something like this. To use proxy-conf, we usually have a proxy.conf.json with something like this:

{
  "/api/*": {
    "target": "http://external-gateway-url",
    "secure": false,
    "logLevel": "debug"
  }
}

and then while running the app use --proxy-config proxy.conf.json. My FE knowledge is out-of-date. You may want to look something like this.

If not, and the call is direct, just the following configuration (also needed for proxy too) in gateway should work:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:3000"
            allowedHeaders: "*"
            allowedMethods:
            - GET
            - POST
Conny answered 8/1, 2022 at 0:52 Comment(16)
no, I don't use ` --proxy-conf`Foreandaft
So your java script is configured with the host name as well? Meaning in the network tab, call is made from localhost:3000 to gateway 1.1.1.1:8080?Conny
I notice, you dont have allowedHeaders: "*" in the configurationConny
Strange, again it's not working. Can I invite you tomorrow on a call and share my screen to show you in details the issue? Can you share your mail for example, please?Foreandaft
Not sure about the call, its weekend :) Can you try using proxy-conf? I will update the answerConny
I found the issue: #70638308 Any idea for a solution?Foreandaft
OPTIONS is something that browser do when there is a call made to a different origin. Dont think you can control that. You can check the repose-headers from the options call to verify if Gateway is configured correctly.Conny
If Gateway is not under your control, you could look into using --proxy-conf, a feature by webpack. CORS is a browser-to-server check. Its doesnt happend with backend-to-backend calls. With proxy, you will just re making calls to the webpack server which in turn will be redirecting it to an external server.Conny
The issue is a bug in Spring Cloud Gateway - OPTIONS requests are blocked so this breaks POST and DELETE requests.Foreandaft
See here github.com/spring-cloud/spring-cloud-gateway/issues/112 and here github.com/spring-cloud/spring-cloud-gateway/issues/2472Foreandaft
Not sure if its a bug. You could wait for spring team to respond. But note that CORS check kicks in when there is a "Origin" header in the request. For GET I dont thinks it's their, but for POST/PUT it is typically there. Enble wiretab logs in gateway to check - cloud.spring.io/spring-cloud-gateway/reference/html/#wiretapConny
With postman things, will work fine as its not a bowser call, which adds these headers.Conny
I send API requests from React app to remote VPS server - CORS should be blocking requests only if all apps are on the same machine.Foreandaft
Yes with POSTMAN I can make successful POST and DELETE requests.Foreandaft
Check about origin - developer.mozilla.org/en-US/docs/Glossary/Origin If anything in domain changes, its a different originConny
Just enable wiretap logs and check Origin header when the reques is from browser vs when it is from postman.Conny
D
6

To solve this issue I set up the following config for my Spring Cloud Gateway

spring:
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://127.0.0.1:3000,http://127.0.0.1:3001"
            allowedHeaders: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTIONS

I reached this solution from this discussion: https://github.com/spring-cloud/spring-cloud-gateway/issues/840

Note that you need to add the default-filters configuration to fix the following issue:

":has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://127.0.0.1:3000, http://127.0.0.1:3000', but only one is allowed."

Dustheap answered 5/4, 2023 at 0:15 Comment(1)
This worked for me. Thank you so much.Glenoid
C
5

Are you running your FE app via --proxy-conf to redirect? I am not a well versed in FE, but dont use changeOrigin: true. If you have to to use changeOrigin: true, it will only work for GET and for others you might have to do something like this. To use proxy-conf, we usually have a proxy.conf.json with something like this:

{
  "/api/*": {
    "target": "http://external-gateway-url",
    "secure": false,
    "logLevel": "debug"
  }
}

and then while running the app use --proxy-config proxy.conf.json. My FE knowledge is out-of-date. You may want to look something like this.

If not, and the call is direct, just the following configuration (also needed for proxy too) in gateway should work:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:3000"
            allowedHeaders: "*"
            allowedMethods:
            - GET
            - POST
Conny answered 8/1, 2022 at 0:52 Comment(16)
no, I don't use ` --proxy-conf`Foreandaft
So your java script is configured with the host name as well? Meaning in the network tab, call is made from localhost:3000 to gateway 1.1.1.1:8080?Conny
I notice, you dont have allowedHeaders: "*" in the configurationConny
Strange, again it's not working. Can I invite you tomorrow on a call and share my screen to show you in details the issue? Can you share your mail for example, please?Foreandaft
Not sure about the call, its weekend :) Can you try using proxy-conf? I will update the answerConny
I found the issue: #70638308 Any idea for a solution?Foreandaft
OPTIONS is something that browser do when there is a call made to a different origin. Dont think you can control that. You can check the repose-headers from the options call to verify if Gateway is configured correctly.Conny
If Gateway is not under your control, you could look into using --proxy-conf, a feature by webpack. CORS is a browser-to-server check. Its doesnt happend with backend-to-backend calls. With proxy, you will just re making calls to the webpack server which in turn will be redirecting it to an external server.Conny
The issue is a bug in Spring Cloud Gateway - OPTIONS requests are blocked so this breaks POST and DELETE requests.Foreandaft
See here github.com/spring-cloud/spring-cloud-gateway/issues/112 and here github.com/spring-cloud/spring-cloud-gateway/issues/2472Foreandaft
Not sure if its a bug. You could wait for spring team to respond. But note that CORS check kicks in when there is a "Origin" header in the request. For GET I dont thinks it's their, but for POST/PUT it is typically there. Enble wiretab logs in gateway to check - cloud.spring.io/spring-cloud-gateway/reference/html/#wiretapConny
With postman things, will work fine as its not a bowser call, which adds these headers.Conny
I send API requests from React app to remote VPS server - CORS should be blocking requests only if all apps are on the same machine.Foreandaft
Yes with POSTMAN I can make successful POST and DELETE requests.Foreandaft
Check about origin - developer.mozilla.org/en-US/docs/Glossary/Origin If anything in domain changes, its a different originConny
Just enable wiretap logs and check Origin header when the reques is from browser vs when it is from postman.Conny
P
4

I had a similar Problem and i did the following:

My application.yml contains to add the CORS Configuration to every route:

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true

Then I configured a spring standard CorsWebFilter Bean. Note for production you should not use * for the AllowedOrigins property. For Development purpose this is perfectly fine.

@Configuration
public class CorsConfiguration extends org.springframework.web.cors.CorsConfiguration {

  @Bean
  public CorsWebFilter corsFilter() {
    org.springframework.web.cors.CorsConfiguration corsConfiguration = new org.springframework.web.cors.CorsConfiguration();
    corsConfiguration.setAllowCredentials(true);
    corsConfiguration.addAllowedOrigin("*");
    corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"));
    corsConfiguration.addAllowedHeader("origin");
    corsConfiguration.addAllowedHeader("content-type");
    corsConfiguration.addAllowedHeader("accept");
    corsConfiguration.addAllowedHeader("authorization");
    corsConfiguration.addAllowedHeader("cookie");
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfiguration);
    return new CorsWebFilter(source);
  }
}

Note: I have the Version 2.3.9.Release for spring boot and spring cloud version is Hoxton.SR10 .

Peltast answered 6/1, 2022 at 13:33 Comment(6)
I tried the code but I get pastebin.com/72REGwi8 with latest stable Spring Cloud. Any idea for a solution?Foreandaft
I expect that * is not allowed anymore. So maybe try corsConfiguration.setAllowedOrigin("<your web adress, e.g. localhost>")Peltast
One additional question: I run Spring Cloud Gateway on a remote VPS server and I run run React application on my local PC. I think in that case CORS should not make a problem. Did you had a such case?Foreandaft
I'm not realy sure about it. But if it is a problem then you may consider running an second gateway on your local machine. So your traffic look like: react app -> local gateway (Here you have to configure cors. Different ports on localhost are different origins!) -> gateway on VPS server.Peltast
Maybe I need to configure something in React app?Foreandaft
There's never a need to allow the origin or cookie request headers. Moreover, you cannot use the wildcard (*) in conjunction with credentialed requests; see developer.mozilla.org/en-US/docs/Web/HTTP/…Uel
M
3

You can add the cors configuration in your security class. Like this

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
    private SecurityRepository securityRepository;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http){
        http
                .cors().configurationSource(request -> {
                    CorsConfiguration configuration = new CorsConfiguration();
                    configuration.setAllowedOrigins(List.of("http://localhost:3000"));
                    configuration.setAllowedMethods(List.of("*"));
                    configuration.setAllowedHeaders(List.of("*"));
                    return configuration;
                });
        http.csrf().disable();
        return http.build();
    }
}
Marhtamari answered 6/9, 2022 at 15:49 Comment(7)
This doesnt work for me because EnableWebFluxSecurity cannot be found.Donalddonaldson
Have you added the spring security dependency in your pom file?Marhtamari
No, that is the thing. Why would you add an entire dependency just for some small functionality? What if you only want CORS without Spring security because these 2 things are completely independant of each other. Spring security is user security where as CORS is a kind of connection security.Donalddonaldson
For the answer I posted to work, you’ll need the security dependency but I think there are other ways to go about it without the dependencyMarhtamari
@Abd Qadr, This option doesn't work for me even with the annotation enabled. You don't have a link to the working code, I don't understand what's the matter?Velez
checkout this link baeldung.com/spring-webflux-corsMarhtamari
Thanks. This is the only required and correct configuration for me when I use spring cloud gateway as oauth2 resource server.Hayman
F
2

The answer from Tr1monster works and is likely the best approach.

A slightly more compact version of the Java configuration:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
public class CorsConfig 
    extends CorsConfiguration
{
    @Bean
    public CorsWebFilter corsFilter()
    {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials( true );
        config.setAllowedOrigins( List.of( "*" ) );
        config.setAllowedMethods( List.of( "GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD" ) );
        config.setAllowedHeaders( List.of( "origin", "content-type", "accept", "authorization", "cookie" ) );

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

        return new CorsWebFilter( source );
    }
}
Forthcoming answered 15/1, 2023 at 11:30 Comment(0)
U
1

Check out the section entitled Simple requests in the MDN Web Docs about CORS:

The only type/subtype combinations allowed for the media type specified in the Content-Type header are:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

Because you use the value application/json for your request's Content-Type header, you need to allow that header in your CORS configuration:

cors-configurations:
  '[/**]':
    allowedOrigins: "http://localhost:3000"
    allowedMethods:
      - GET
      - POST
    allowedHeaders:
      - Content-Type

Uel answered 6/1, 2022 at 11:56 Comment(9)
Do you know how I can configure this using Java Bean configuration?Foreandaft
@PeterPenzov No. That would be a different question anyway.Uel
@PeterPenzov Have you tried the solution above?Uel
I will try it later today and let you know hat is the result.Foreandaft
I get again Access to XMLHttpRequest at 'http://1.1.1.1:8080/api/merchants/onboarding/countries' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.Foreandaft
@PeterPenzov Well, that's progress: no mention of the preflight this time, which means it succeeded. Now the actual request is failing, for some reason. Can you add a screenshot of the response to the actual (POST) request?Uel
Please see this post description: #70562043 I added the payload thereForeandaft
@PeterPenzov You haven't added the response to the POST request. Based on the error message you're now getting, you should now get a response to that request.Uel
I will make again later today test requests and let you know about the result.Foreandaft
H
1

After lost hours, I did this:

spring: application: name: gateway cloud: loadbalancer: ribbon: enabled: false

gateway:
  default-filters:
    - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
  globalcors:
    corsConfigurations:
      '[/**]':
        allowedOrigins: "http://localhost:3000/"
        allowedHeaders: "*"
        allowedMethods:
          - GET
          - POST
          - DELETE
          - PUT
          - PATCH
  routes:
    - id: service-auth
      uri: lb://service-auth
      predicates:
        - Path=/api/v1/auth/**
Hough answered 13/12, 2023 at 5:49 Comment(0)
K
1

It might also be that you are enabling CORS in multiple places.

I had this in my Spring Cloud Gateway properties.yaml file:

  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:3000"
            allowedHeaders: "*"
            allowedMethods:
              - GET
              - POST

I also had this in my SecurityFilterChain (different microservice, to which gateway directs):

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception
    {
        http
                .cors()
                .and()
                ...

And I also had @CrossOrigin sprinkled on all of my @RestControllers.

What helped me was removing all except for the one in my properties.yaml file at the gateway (first configuration).

Keystroke answered 26/6 at 14:33 Comment(0)
M
1

I resolved same issue with spring boot 3+ version, by adding cors in application.yml and securityConfig file too without any corsWebFilter, it is working now.

application.yml

gateway:
  default-filters:
    - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
  globalcors:
      corsConfigurations:
        '[/**]':
          allowedOrigins: "http://localhost:4200"
          allowedMethods: "*"
          allowedHeaders: "*"
  routes:

SecurityConfig

@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { http.cors().configurationSource(request -> { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of("http://localhost:4200")); configuration.setAllowedMethods(List.of("")); configuration.setAllowedHeaders(List.of("")); configuration.setAllowCredentials(true); // Allow credentials if needed return configuration; }) .and() .csrf().disable() .authorizeExchange(exchanges -> exchanges .pathMatchers("/login**", "/oauth2/", "/idleTimeExpired").permitAll() .anyExchange().authenticated() ) .oauth2Login(oauth2Login -> oauth2Login .authenticationSuccessHandler(new CustomAuthenticationSuccessHandler()) ).oauth2Client();

    http.addFilterBefore(sessionInvalidationFilter, SecurityWebFiltersOrder.OAUTH2_AUTHORIZATION_CODE);

    return http.build();
}

From Angular u need to pass headers

const url = apiUrl + /timesheet/getUser?emailId=${email};

const headers = new HttpHeaders({ 'Content-Type': 'application/json' });

return this.http.get(url, { headers, withCredentials: true });

by doing all above config now cors issue resolved.

Mcgrody answered 31/7 at 9:20 Comment(0)
M
0

I want to share with you the solution that worked for me hoping to help whoever is facing the same issue:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
    @Autowired
    private ReactiveClientRegistrationRepository clientRegistrationRepository;

    @Bean
    SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {

        CorsConfiguration cors_config = new CorsConfiguration();
        cors_config.setAllowCredentials(true);
        cors_config.applyPermitDefaultValues();
        cors_config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "null"));
        cors_config.setAllowedMethods(List.of("GET","POST","OPTIONS","DELETE"));
        cors_config.setAllowedHeaders(List.of("*"));

        http.cors().configurationSource(source -> cors_config)
                .and()
                .csrf().disable()
                .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
                .oauth2Login()//Setting Oauth2Login
                .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("<MyLogoutURL>")).and()
                .logout(logout -> logout //Setting Oauth2Logout
                        .logoutHandler(logoutHandler())
                        .logoutSuccessHandler(oidcLogoutSuccessHandler()));
        return http.build();
    }

    private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
        OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
                new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
        // Sets the location that the End-User's User Agent will be redirected to
        // after the logout has been performed at the Provider
        oidcLogoutSuccessHandler.setPostLogoutRedirectUri("http://google.com");
        return oidcLogoutSuccessHandler;
    }

    private DelegatingServerLogoutHandler logoutHandler() {
        //Invalidate session on logout
        return new DelegatingServerLogoutHandler(
                new SecurityContextServerLogoutHandler(), new WebSessionServerLogoutHandler());
    }
}

Be sure to have cors enabled on Keycloak too, navigate to realm->clients->settings->weborigins and submit your permitted origins.

Mac answered 5/2, 2023 at 14:54 Comment(0)
E
0

I faced this issue recently and this approach resolved it...

@Configuration
public class WebFluxConfiguration implements WebFluxConfigurer {

//properties

@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
            .allowedOrigins(allowedOrigins)
            .allowedHeaders(allowedHeaders)
            .allowedMethods(allowedMethods)
            .allowCredentials(allowCredentials)
            .maxAge(maxAge);
   }
}
Elson answered 15/2 at 8:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.