Spring Boot Security CORS

118,542

Solution 1

Instead of using the CorsRegistry you can write your own CorsFilter and add it to your security configuration.

Custom CorsFilter class:

public class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request= (HttpServletRequest) servletRequest;

        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Allow-Credentials", true);
        response.setHeader("Access-Control-Max-Age", 180);
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

Security config class:

@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    CorsFilter corsFilter() {
        CorsFilter filter = new CorsFilter();
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(corsFilter(), SessionManagementFilter.class) //adds your custom CorsFilter
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
                .formLogin()
                    .successHandler(ajaxSuccessHandler)
                    .failureHandler(ajaxFailureHandler)
                    .loginProcessingUrl("/authentication")
                    .passwordParameter("password")
                    .usernameParameter("username")
                .and()
                .logout()
                    .deleteCookies("JSESSIONID")
                    .invalidateHttpSession(true)
                    .logoutUrl("/logout")
                    .logoutSuccessUrl("/")
                .and()
                .csrf().disable()
                .anonymous().disable()
                .authorizeRequests()
                .antMatchers("/authentication").permitAll()
                .antMatchers("/oauth/token").permitAll()
                .antMatchers("/admin/*").access("hasRole('ROLE_ADMIN')")
                .antMatchers("/user/*").access("hasRole('ROLE_USER')");
    }
}

Solution 2

Option 1 (Use WebMvcConfigurer bean):

The CORS configuration that you started with is not the proper way to do it with Spring Boot. You need to register a WebMvcConfigurer bean. Reference here.

Example Spring Boot CORS configuration:

@Configuration
@Profile("dev")
public class DevConfig {

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

}

This will provide the CORS configuration for a basic (no security starter) Spring Boot application. Note that CORS support exists independent of Spring Security.

Once you introduce Spring Security, you need to register CORS with your security configuration. Spring Security is smart enough to pick up your existing CORS configuration.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .cors().and()              
         ....

Option 2 (Use CorsConfigurationSource bean):

The first option I described is really from the perspective of adding Spring Security to an existing application. If you are adding Spring Security from the get-go, the way that is outlined in the Spring Security Docs involves adding a CorsConfigurationSource bean.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // by default uses a Bean by the name of corsConfigurationSource
            .cors().and()
            ...
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Solution 3

This is quite clean and doesn't require any extra configurations. Pass asterisks where you want all option to be valid (like I did in setAllowedHeaders).

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.cors().configurationSource(request -> {
      var cors = new CorsConfiguration();
      cors.setAllowedOrigins(List.of("http://localhost:4200", "http://127.0.0.1:80", "http://example.com"));
      cors.setAllowedMethods(List.of("GET","POST", "PUT", "DELETE", "OPTIONS"));
      cors.setAllowedHeaders(List.of("*"));
      return cors;
    }).and()...
  }
}

Solution 4

I have a React based web client, and my backend REST API is running Spring Boot Ver 1.5.2

I wanted to quickly enable CORS on all controller route requests from my client running on localhost:8080. Inside my security configuration, I simply added a @Bean of type FilterRegistrationBean and got it working easily.

Here is the code:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AuthConfiguration extends WebSecurityConfigurerAdapter {

....
....

  @Bean
  public FilterRegistrationBean corsFilter() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin(corsAllowedOrigin); // @Value: http://localhost:8080
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
    bean.setOrder(0);
    return bean;
  }

  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {    
      httpSecurity
        .authorizeRequests()
        .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // **permit OPTIONS call to all**
        ....
  }

....
....

}

You can refer Spring Boot docs here

Solution 5

Currently the OPTIONS requests are blocked by default if security is enabled.

Just add an additional bean and preflight requests will be handled correctly:

   @Bean
   public IgnoredRequestCustomizer optionsIgnoredRequestsCustomizer() {
      return configurer -> {
         List<RequestMatcher> matchers = new ArrayList<>();
         matchers.add(new AntPathRequestMatcher("/**", "OPTIONS"));
         configurer.requestMatchers(new OrRequestMatcher(matchers));
      };
   }

Please note that depending on your application this may open it for potential exploits.

Opened issue for a better solution: https://github.com/spring-projects/spring-security/issues/4448

Share:
118,542
user1935987
Author by

user1935987

Updated on March 25, 2021

Comments

  • user1935987
    user1935987 about 3 years

    I have a problem with CORS filter on spring security URL's. It doesn't set Access-Control-Allow-Origin and other exposed header on URL's belonging to spring sec (login/logout) or filtered by Spring Security.

    Here are the configurations.

    CORS:

    @Configuration
    @EnableWebMvc
    public class MyWebMvcConfig extends WebMvcConfigurerAdapter {
    ********some irrelevant configs************
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/*").allowedOrigins("*").allowedMethods("GET", "POST", "OPTIONS", "PUT")
                    .allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
                            "Access-Control-Request-Headers")
                    .exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
                    .allowCredentials(true).maxAge(3600);
        }
    }
    

    Security:

    @Configuration
    @EnableWebSecurity
    public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
                    .formLogin()
                        .successHandler(ajaxSuccessHandler)
                        .failureHandler(ajaxFailureHandler)
                        .loginProcessingUrl("/authentication")
                        .passwordParameter("password")
                        .usernameParameter("username")
                    .and()
                    .logout()
                        .deleteCookies("JSESSIONID")
                        .invalidateHttpSession(true)
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/")
                    .and()
                    .csrf().disable()
                    .anonymous().disable()
                    .authorizeRequests()
                    .antMatchers("/authentication").permitAll()
                    .antMatchers("/oauth/token").permitAll()
                    .antMatchers("/admin/*").access("hasRole('ROLE_ADMIN')")
                    .antMatchers("/user/*").access("hasRole('ROLE_USER')");
        }
    }
    

    So, if I make a request to the url's which are not listened by security - CORS headers are set. Spring security URL's - not set.

    Spring boot 1.4.1