title: Zuul Proxy CORS header contains multiple values, headers repeated twice - Java Spring Boot CORS filter config
body: <p>Why would I be getting every CORS header doubled? I am using the Zuul Proxy to have the request to a service proxied through an API gateway.</p>
  
  <p>I must have something misconfigured with my spring security filtering order.</p>
  
  <p>When I access a route that requires authentication I am getting an error like: </p>
  
  <h2>Request to service through API Gateway error</h2>
  
  <pre><code>XMLHttpRequest cannot load https://myservice.mydomain.com:8095/service/v1/account/txHistory?acctId=0.  
  The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. 
  Origin 'http://localhost:9000' is therefore not allowed access.
  </code></pre>
  
  <h2>Chrome network log</h2>
  
  <p>I checked the response in Chrome devtools and sure enough the CORS headers are repeated twice: <a href="https://i.stack.imgur.com/sTLVo.png" rel="noreferrer"><img src="https://i.stack.imgur.com/sTLVo.png" alt="CORS Headers repeated"></a></p>
  
  <p>So this looks like somehow my CORS filter is being called twice for each reply. I don't know why that would be happening at this point. It could be that my filter is added before the ChannelProcessingFilter.</p>
  
  <h2>Code for API Gateway CORS filter:</h2>
  
  <pre><code>public class SimpleCORSFilter implements Filter {
  
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {}
  
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
          HttpServletResponse res = (HttpServletResponse) response;
          res.setHeader("Access-Control-Allow-Origin", "*");
          res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
          res.setHeader("Access-Control-Max-Age", "3600");
          res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, x-requested-with, Cache-Control");
          chain.doFilter(request, res);
      }
  
      @Override
      public void destroy() {}
  }
  </code></pre>
  
  <h2>My API Gateway security configuration:</h2>
  
  <pre><code>@Configuration
  @EnableWebSecurity
  @EnableGlobalMethodSecurity(prePostEnabled = true)
  public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
      @Bean
      @Override
      public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
      }
  
      @Inject
      public void setUserDetailsService(UserDetailsService userDetailsService) {
          this.userDetailsService = userDetailsService;
      }
  
      private UserDetailsService userDetailsService;
  
      @Override
      protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
          authenticationManagerBuilder.userDetailsService(userDetailsService)
                  .passwordEncoder(new BCryptPasswordEncoder());
      }
  
      @Override
      protected void configure(HttpSecurity httpSecurity) throws Exception {
          httpSecurity
              .sessionManagement()
                  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                  .and()
             .authorizeRequests()
                  .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                  .antMatchers("/health","/metrics", "/v1/users/register").permitAll()
                  .antMatchers("/mappings", "/v1/**", "/service/**").authenticated()
                  .and()
              .httpBasic()
                  .realmName("apiRealm")
                  .and()
              .csrf()
                  .disable()
              .headers()
                  .frameOptions().disable()
              .and().addFilterBefore(new SimpleCORSFilter(), ChannelProcessingFilter.class);
      }
  
  }
  </code></pre>
  
  <p>I could solve this by checking if the header is null and then setting it only if it is empty or null, though that does not seem like the best solution. I would like to understand what I have done to cause the headers to be preset twice.</p>