5

I'm trying to configure Spring Security to make it support CORS. Thanks to this post, Spring security CORS Filter, I've made it work on my localhost with Spring Boot with this configuration code :

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
        .and()
        .antMatcher("/api/**")
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
        .antMatchers(HttpMethod.POST, "/api/login").permitAll()
        .antMatchers(HttpMethod.GET, "/api/websocket/**").permitAll()
        .antMatchers("/api/**").authenticated()
        .and()
        .addFilterBefore(new JWTLoginFilter("/api/login", HttpMethod.POST, authenticationManager(), tokenAuthenticationService, myUserService), UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(new JWTAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class)
        .csrf().disable();
    }

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    final CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(ImmutableList.of("*"));
    configuration.setAllowedMethods(ImmutableList.of("HEAD",
            "GET", "POST", "PUT", "DELETE", "PATCH"));
    // setAllowCredentials(true) is important, otherwise:
    // The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
    configuration.setAllowCredentials(true);
    // setAllowedHeaders is important! Without it, OPTIONS preflight request
    // will fail with 403 Invalid CORS request
    configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

}

But when I deploy my apps on remote Tomcat servers, it doesn't work :

 Access to XMLHttpRequest at 'http://xxx:9080/yyy/api/user/findByLogin/?login=zzz' from origin 'http://xxx:10080' 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.

Here's a screenshot of the failing OPTIONS request : enter image description here

And the working request on my localhost : enter image description here

Is my configuration class enough or do I need to set something in Tomcat settings ? Thank you

Clément Picou
  • 4,011
  • 2
  • 15
  • 18
  • Can you check whether your OPTIONS request gives CORS error? If so, add it to your allowed methods and try again. – Viswa Oct 31 '19 at 13:01
  • The OPTIONS request returns a 403. I've added a screenshot. – Clément Picou Oct 31 '19 at 14:18
  • Can you look into this post? https://stackoverflow.com/questions/43699343/handling-options-and-cors-when-using-a-sign-in-filter-instead-of-controller – Viswa Oct 31 '19 at 16:18
  • It's the same, the solution works locally but not on Tomcat. – Clément Picou Oct 31 '19 at 16:42
  • How does your pom.xml and main class look like? Please try to provide a [mcve]. – Selaron Nov 07 '19 at 15:38
  • Why didn't you allow OPTIONS method in your corsConfigurationSource? – amant singh Nov 07 '19 at 19:01
  • I've tried the same configuration in my local tomcat and then using Postman, I've changed the Origin header value to http:// mycompany.com and I was able to access the request URL. So to me, it seems like, your organization's server settings might be removing the CORS headers(We had the similar issue in our organization with the Oracle HTTP Server (OHS) where the mod_headers config was removing the CORS response headers). To make sure the issue is not with your app, If you can deploy the same application onto another computer (colleague's machine) and test it to rule out config issue. – Ramu Nov 08 '19 at 05:53
  • It sounds to me like the filter is not getting registered correctly when you deploy to tomcat. If you are deploying a war file to tomcat then you need to make sure the filters are registered in the `web.xml` file. One way to tell would be to simply dump a stack trace on your controller endpoint (ie even for a GET), that will show the filters that are being registered. Also turn on spring debugging as well, that should help show up and issues with config. – stringy05 Nov 10 '19 at 23:03
  • Are there any web server (Nginx, Apache HTTP Server) or web application firewall (WAF) in front of the remote Tomcat? If CORS works on a local Tomcat, then like @Ramu says the problem might be in the web server that forwards requests to the Tomcat. – Eugene Khyst Nov 12 '19 at 11:05
  • Just for an experiment try setting an exact values for AllowedOrigins instead of the wildcard: `configuration.setAllowedOrigins(ImmutableList.of("http://xxx:10080", "http://localhost:8082"));` – Eugene Khyst Nov 12 '19 at 11:13
  • Can you confirm that there is no front web server set up before request reaches tomcat ? A web server can essentially override any header that your app server is sending, thus voiding any CORS configuration done in tomcat. – Gaurav Nov 13 '19 at 08:09

7 Answers7

1

Spring security provides a way to configure CORS in http configurer, there's a much cleaner approach to add CORS filter to the application-

@Component 
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyCORSFilterClass implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}

@Override
public void destroy() {
}
}

Ordering the filter with the highest precedence makes sure that MyCORSFilterClassimplementation of javax.servlet.Filter is the first one in the chain.

0

Tomcat has its own cors filter too and if you using an other server before tomcat ( like nodejs, apache server vs ) check its cors filters too.

Burak Akyıldız
  • 1,550
  • 15
  • 25
0

Check to see if the tomcat server has a conflicting CORS filter configured in $CATALINA_BASE/conf/web.xml

Izzy
  • 239
  • 1
  • 5
0
  1. Try putting a debug point in the corsConfigurationSource() method in your local and check if it is being executed. If it is not getting executed, investigate the reason - probably by enabling debug logs of Spring and/or rechecking Spring configuration.

  2. Also, try adding OPTIONS to setAllowedMethods

    configuration.setAllowedMethods(ImmutableList.of("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
    
seenukarthi
  • 8,241
  • 10
  • 47
  • 68
Smile
  • 3,832
  • 3
  • 25
  • 39
0
  1. Your allow methods should have "OPTIONS" as well.
  2. Do you own the web server? Is there any chance that the web server have CORS as well?
Liem Le
  • 581
  • 7
  • 17
0

Just configure CorsFilter in order to add the relevant CORS response headers (like Access-Control-Allow-Origin) using the provided CorsConfigurationSource. Read its documentation for more. Since you already have UrlBasedCorsConfigurationSource, you can have the filter configured like below:

@Bean
  public FilterRegistrationBean corsFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(corsConfigurationSource()));
    bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return bean;
  }

Just have the bean mentioned above in your config and hopefully it works.

Here is the full config I've of which you only need some part as mentioned above:

@Bean
  public FilterRegistrationBean corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    config.addExposedHeader("Content-Disposition");
    source.registerCorsConfiguration("/**", config);
    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
    bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return bean;
  }
user3487063
  • 3,672
  • 1
  • 17
  • 24
0

The response 403 reflects an authorization failure. It could be that your server is set up to ask authorization for options request. You have to make sure the options is configured to send successful response (2xx status code) to allow the browser to send the actual request. 2xx response notifies the browser the server handles CORS requests.

The reason your request worked locally could be that you are authorized when you made the request. So check your authentication to make sure its correct. As such pre flight requests don't send any authorization headers so you shouldn't be expecting on the server side.

s7vr
  • 73,656
  • 11
  • 106
  • 127