'Spring Boot - The 'Access-Control-Allow-Origin' header contains multiple values but expect only one

I read all posts on StackOverflow on this topic but did not find answer as they all are about difference scenario than mine.

My spring boot API uses Spring Security and is accessed by Angular client.

The Angular client can query various endpoints to which my API adds "Access-Control-Allow-Origin" as "http://localhost:4200" using CorsConfiguration setAllowedOrigins method. I get responses as expected including this header, so all good.

However, one of the endpoints calls another API. That API also has its own "Access-Control-Allow-Origin" set to "*".

If my client queries this endpoint, I get following error:

"CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:4200, *', but only one is allowed."

So, the 2nd API adds this header with * in it, then my API also adds "http://localhost:4200" and I end up with double entry in Access-Control-Allow-Origin header like "http://localhost:4200, *".

I would like to fix this issue but I dont know how.

My API security configuration is adding CorsConfiguration like below:

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .authenticationProvider(...);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
                cors()
                .and()
                .csrf().disable()
                .and()
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {

        final CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(Arrays.asList("*"));

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

        return source;
    }
}

and all my controllers are annotated with:

@CrossOrigin(origins = "http://localhost:4200")

Above, I call setAllowedOrigins(Arrays.asList("http://localhost:4200")) to set 'Access-Control-Allow-Origin' header in my API.

All endpoints in my API work as expected except one endpoint which calls another API. That API also sets 'Access-Control-Allow-Origin' to "" which then ends up back in my API resulting in double values: "http://localhost:4200" and "" in 'Access-Control-Allow-Origin'.

Here is my Controller and Service that is failing. As said, all others (which do not call another API) are working fine.

Below is my controller, it calls my service provided below. The service call succeeds and returns response. However, once this response is sent to the client, I get the above error.

@RestController
@RequestMapping("/wagen")
@CrossOrigin(origins = "http://localhost:4200")
public class WagenController {

    public WagenController(WagenService service) {
          wagenService = service;
    }

    @PostMapping
    public ResponseEntity<WagenResponse> getWagen(@RequestBody WagenRequest wagenRequest, @RequestHeader HttpHeaders httpHeaders, HttpServletResponse response) {
        ResponseEntity<WagenResponse> wagenResponse = wagenService.getWagen(wagenRequest, httpHeaders);
        return wagenResponse;
    }
}

Here is my service. It uses restTemplate to make a call to the other API. All this works fine:

@Override
public ResponseEntity<WagenResponse> getWagen(WagenRequest wagenRequest, HttpHeaders httpHeaders) {
    List<String> authHeader = httpHeaders.get("authorization");
    HttpHeaders headers = new HttpHeaders();
    // add BasicAuth for the other API before calling it in 
    //  below restTemplate call
    headers.add("Authorization", authHeader.get(0)); 

    HttpEntity<WagenRequest> request = new HttpEntity<WagenRequest>(wagenRequest, headers);
    ResponseEntity<WagenResponse> wagenResponse = restTemplate.postForEntity(uri, request, WagenResponse.class);
    return wagenResponse;
}

How do I fix this issue so that I dont have 2nd entry in 'Access-Control-Allow-Origin'? Or some other way?

I dont have a way to change what other API is sending to me.

Here is the screenshot of network tab in dev tools and error shown in console (This works fine in PostMan). Interestingly, Firefox dev tools show that the :

enter image description here

enter image description here



Solution 1:[1]

The problem is in how you're calling that other API that's the source of the extra CORS header.

In your endpoint, reproduced here, you call getWagen and retrieve a ResponseEntity as a response:

@PostMapping
public ResponseEntity<WagenResponse> getWagen(@RequestBody WagenRequest wagenRequest, @RequestHeader HttpHeaders httpHeaders, HttpServletResponse response) {
  ResponseEntity<WagenResponse> wagenResponse = wagenService.getWagen(wagenRequest, httpHeaders);
  return wagenResponse;
}

The problem is in what you're doing with the response from the Wagen API.

A ResponseEntity is the whole response from the other API -- including the headers. If you put a breakpoint on that return statement, you'll be able to explore the wagenResponse object and see that the headers include the "*" CORS header as returned by the other API.

By returning the ResponseEntity as-is, you're going to be returning those API headers as well. Then the decorators and annotations you have in your app will add your CORS headers on top of those returned by the Wagen API ... hence your problem.

When you get back the ResponseEntity<WagenResponse> from the getWagen method, you need to pick out the bits you actually want -- the body, the status, any non-CORS headers -- and then generate a new ResponseEntity to return to the caller.

Solution 2:[2]

I'm pretty new to Java Spring Boot but here's how I handled CORS: Under main():

@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                .allowedOrigins("http://localhost:4200");
        }
    };
}

I hope it can help you!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Roddy of the Frozen Peas
Solution 2