3

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
Peter Penzov
  • 1,126
  • 134
  • 430
  • 808

7 Answers7

4

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
Dhananjay
  • 1,140
  • 1
  • 12
  • 28
  • no, I don't use ` --proxy-conf` – Peter Penzov Jan 08 '22 at 02:29
  • 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 http://1.1.1.1:8080? – Dhananjay Jan 08 '22 at 03:26
  • I notice, you dont have `allowedHeaders: "*"` in the configuration – Dhananjay Jan 08 '22 at 03:36
  • 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? – Peter Penzov Jan 08 '22 at 03:49
  • Not sure about the call, its weekend :) Can you try using proxy-conf? I will update the answer – Dhananjay Jan 08 '22 at 03:55
  • I found the issue: https://stackoverflow.com/questions/70637808/disable-options-request-before-post-in-react Any idea for a solution? – Peter Penzov Jan 09 '22 at 01:46
  • 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. – Dhananjay Jan 09 '22 at 16:51
  • 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. – Dhananjay Jan 09 '22 at 16:54
  • The issue is a bug in Spring Cloud Gateway - OPTIONS requests are blocked so this breaks POST and DELETE requests. – Peter Penzov Jan 09 '22 at 17:06
  • See here https://github.com/spring-cloud/spring-cloud-gateway/issues/112 and here https://github.com/spring-cloud/spring-cloud-gateway/issues/2472 – Peter Penzov Jan 09 '22 at 17:07
  • 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 - https://cloud.spring.io/spring-cloud-gateway/reference/html/#wiretap – Dhananjay Jan 09 '22 at 17:19
  • With postman things, will work fine as its not a bowser call, which adds these headers. – Dhananjay Jan 09 '22 at 17:20
  • 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. – Peter Penzov Jan 09 '22 at 17:21
  • Yes with POSTMAN I can make successful POST and DELETE requests. – Peter Penzov Jan 09 '22 at 17:21
  • Check about origin - https://developer.mozilla.org/en-US/docs/Glossary/Origin If anything in domain changes, its a different origin – Dhananjay Jan 09 '22 at 17:25
  • Just enable wiretap logs and check Origin header when the reques is from browser vs when it is from postman. – Dhananjay Jan 09 '22 at 17:26
3

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 .

Tr1monster
  • 328
  • 3
  • 13
  • I tried the code but I get https://pastebin.com/72REGwi8 with latest stable Spring Cloud. Any idea for a solution? – Peter Penzov Jan 07 '22 at 00:01
  • I expect that * is not allowed anymore. So maybe try `corsConfiguration.setAllowedOrigin("")` – Tr1monster Jan 07 '22 at 07:32
  • 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? – Peter Penzov Jan 07 '22 at 08:21
  • 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. – Tr1monster Jan 07 '22 at 08:46
  • Maybe I need to configure something in React app? – Peter Penzov Jan 07 '22 at 09:00
  • 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 https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards – jub0bs Jan 09 '22 at 10:07
2

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();
    }
}
Abd Qadr
  • 53
  • 7
  • This doesnt work for me because EnableWebFluxSecurity cannot be found. – Martijn Hiemstra Sep 08 '22 at 18:51
  • Have you added the spring security dependency in your pom file? – Abd Qadr Sep 12 '22 at 14:09
  • 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. – Martijn Hiemstra Sep 12 '22 at 16:45
  • 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 dependency – Abd Qadr Sep 18 '22 at 20:07
  • @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? – alexmntmnk Nov 15 '22 at 13:57
  • checkout this link https://www.baeldung.com/spring-webflux-cors – Abd Qadr Nov 15 '22 at 20:36
  • Thanks. This is the only required and correct configuration for me when I use spring cloud gateway as oauth2 resource server. – cyper Feb 09 '23 at 14:41
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 );
    }
}
lars
  • 640
  • 4
  • 10
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

jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • Do you know how I can configure this using Java Bean configuration? – Peter Penzov Jan 06 '22 at 12:02
  • @PeterPenzov No. That would be a different question anyway. – jub0bs Jan 06 '22 at 12:03
  • @PeterPenzov Have you tried the solution above? – jub0bs Jan 06 '22 at 12:08
  • I will try it later today and let you know hat is the result. – Peter Penzov Jan 06 '22 at 12:08
  • 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.` – Peter Penzov Jan 07 '22 at 00:06
  • @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? – jub0bs Jan 07 '22 at 09:19
  • Please see this post description: https://stackoverflow.com/questions/70561543/configure-cors-policy-for-spring-cloud-gateway I added the payload there – Peter Penzov Jan 07 '22 at 09:36
  • @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. – jub0bs Jan 07 '22 at 09:49
  • 1
    I will make again later today test requests and let you know about the result. – Peter Penzov Jan 07 '22 at 09:49
1

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."

Juan Cabello
  • 471
  • 6
  • 7
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.

Centuri0n
  • 59
  • 1
  • 6