After I added CORS support to my microservices infrastructure I faced with problem with zuul forwarding. Instead of forwarding them with POST method Zuul redirects browser requests using GET method.

When browser downloads html+js from UI server (in my case heroku-ui) one of the js script makes AJAX request(POST) to zuul service (heroku-gateway).

$.ajax({
            url : GATEWAY_SERVER_URL + '/results',
            type : 'POST',
            data : JSON.stringify(data),
            contentType : "application/json; charset=utf-8",
            dataType : "json",
            crossDomain: true
        });

Below life-cycle of such request: http://www.plantuml.com/plantuml/png/SoWkIImgAStDuNBEAChFJLNGjLE8AYtDKR1ICDHJy2yeoSpFArO8IYqiJIqkuGB9W5H0r0BL626hAIu0qgcO1XdhAGJu1uVWDiL43oYNabcKcboYK9AlK9nQL9QPZ6nGC5W4fDGKqr5GIHGJpV2w2s6SkPw2ksi8NgCim4eGnZYavgK0mmi0

1. Preflight Request (OPTIONS) https://heroku-gatewaay.herokuapp.com/api/results

Request headers:

  • Access-Control-Request-Method : POST
  • Origin : https://heroku-ui.herokuapp.com
  • Access-Control-Request-Headers : content-type

Response headers:

  • Access-Control-Allow-Origin : *
  • Access-Control-Allow-Methods : GET,PUT,POST,DELETE
  • Access-Control-Allow-Headers : content-type

HTTP status - 200

3. POST Request https://heroku-gatewaay.herokuapp.com/api/results

Request headers:

  • Origin : https://heroku-ui.herokuapp.com
  • Content-Type : application/json; charset=UTF-8

Response headers:

  • Location : https://heroku-multiplication.herokuapp.com/results

HTTP status - 302

5. Preflight Request (OPTIONS) https://heroku-multiplication.herokuapp.com/results

Request headers:

  • Access-Control-Request-Method : GET
  • Origin : null

Response headers:

  • Access-Control-Allow-Origin : *
  • Access-Control-Allow-Methods : GET,PUT,POST,DELETE
  • Access-Control-Allow-Headers : content-type

HTTP status - 200

7. GET Request (expected POST) https://heroku-multiplication.herokuapp.com/results

Request headers:

  • Origin : null
  • Content-Type : application/json; charset=UTF-8

Response headers:

  • Access-Control-Allow-Origin : *

In step 7 I expects POST request but GET is sent.

Probably root cause is HTTP status 302 of http responses https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#attr2

[2] The specification had no intent to allow method changes, but practically there are user agents out there doing this. 307 has been created to remove the ambiguity of the behavior when using non-GET methods.


Components:

1) Zuul (spring-cloud-starter-netflix-zuul of verion 'Greenwich.M1' and spring-boot-starter of version 2.1.0.RELEASE) Configuration:

    @Configuration
    @EnableWebMvc
    public class WebConfiguration implements WebMvcConfigurer  {
        @Override
        public void addCorsMappings(final CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedHeaders("POST", "PUT", "GET", "DELETE")
                    .allowedHeaders("*");
        }
    }
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()
                //if X-Forwarded-Proto then use https (heroku)
                .requiresChannel()
                .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
                .requiresSecure();
        }

        @Bean
        public CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            //configuration.setAllowCredentials(true);
            configuration.setAllowedOrigins(Arrays.asList(CorsConfiguration.ALL));
            configuration.setAllowedMethods(Arrays.asList(HttpMethod.GET.name(), HttpMethod.PUT.name(), 
                                                          HttpMethod.POST.name(), HttpMethod.DELETE.name()));
            configuration.setAllowedHeaders(Arrays.asList(CorsConfiguration.ALL));

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

            return source;
        }
    }

2) Spring-boot microservice (spring-boot-starter of version 2.1.0.RELEASE) Configuration

    @Configuration
    @EnableWebMvc
    public class WebConfiguration implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(final CorsRegistry registry) {
            registry.addMapping("/**");
        }
    }
    @EnableWebSecurity
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()
                //if X-Forwarded-Proto then use https (heroku)
                .requiresChannel()
                .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
                .requiresSecure();
        }

        @Bean
        public CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            //configuration.setAllowCredentials(true);
            configuration.setAllowedOrigins(Arrays.asList(CorsConfiguration.ALL));
            configuration.setAllowedMethods(Arrays.asList(HttpMethod.GET.name(), HttpMethod.PUT.name(), 
                                                          HttpMethod.POST.name(), HttpMethod.DELETE.name()));
            configuration.setAllowedHeaders(Arrays.asList(CorsConfiguration.ALL));

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

            return source;
        }
    }

Comment From: spencergibb

Greenwich.M1 is a milestone, can you update your version to Greenwich.SR1 which is the latest?

Comment From: StarReider

Thank you, it is good point. I've updated spring-cloud version of all my microservices to "Greenwich.SR1" but problem is still reproduces You can find chrome network logs in attachment Network Sniffer.zip

Comment From: ryanjbaxter

I dont understand why you think this is a problem with Zuul. Zuul is just proxing the 302 response back to your client. If your client then makes a GET as a result of that, that is out of the control of Zuul.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: StarReider

Because zuul makes redirection to particular service. And when zuul does it "Access-Control-Request-Method" is lost.

Comment From: gpugems

I am facing same issue when forwarding OPTIONS request. Zuul tranform that to a GET Request.

Comment From: spencergibb

This module has entered maintenance mode. This means that the Spring Cloud team will no longer be adding new features to the module. We will fix blocker bugs and security issues, and we will also consider and review small pull requests from the community.