Clément Picou
Clément Picou

Reputation: 4072

Tomcat 8 and Spring Security Cors

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

Upvotes: 5

Views: 3876

Answers (7)

s7vr
s7vr

Reputation: 75984

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.

Upvotes: 0

user3487063
user3487063

Reputation: 3682

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;
  }

Upvotes: 0

Liem Le
Liem Le

Reputation: 591

  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?

Upvotes: 0

Smile
Smile

Reputation: 4088

  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"));
    

Upvotes: 0

Izzy
Izzy

Reputation: 238

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

Upvotes: 0

Burak Akyıldız
Burak Akyıldız

Reputation: 1644

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.

Upvotes: 0

Shubham Shah
Shubham Shah

Reputation: 66

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.

Upvotes: 1

Related Questions