43

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

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
user1935987
  • 3,136
  • 9
  • 56
  • 108

13 Answers13

60

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;
    }
}
The Gilbert Arenas Dagger
  • 12,071
  • 13
  • 66
  • 80
  • 2
    Seems the bean name must be 'corsConfigurationSource' or it is not picked up? – ThomasRS Mar 04 '19 at 11:55
  • I got error at cors(). 'The method cors() is undefined for the type HttpSecurity' – Pawan Tiwari Mar 27 '19 at 13:06
  • @PawanTiwari use in this method signature `protected void configure(HttpSecurity http) ` – silentsudo Apr 09 '19 at 12:19
  • @ThomasRS Yes, the method must be `corsConfigurationSource()` or you need to name your bean with `@Bean("corsConfigurationSource")`. The code that looks for this bean is `org.springframework.security.config.annotation.web.configurers.CorsConfigurer` – Matthew Buckett Feb 17 '20 at 10:13
  • "You need to register CORS with your security configuration." This sentence saved my day. – Jacky Jun 25 '20 at 08:30
  • 1
    For those who used this solution (Option 2) and still had CORS errors in the browser add the following: `configuration.setAllowedHeaders(Arrays.asList("*"));` – Orachigami Jul 11 '22 at 16:59
48

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')");
    }
}
mika
  • 2,495
  • 22
  • 30
  • seems like this was the key `.addFilterBefore(corsFilter(), SessionManagementFilter.class)` thanks. no tutorials pointing on it. – user1935987 Oct 30 '16 at 07:59
  • Although this would work, the way it should be done is couple spring mvc with spring security by doing what @The Gilbert Arenas Dagger suggests in his Option 1. – Daniel Rijkhof Aug 07 '19 at 09:31
  • 2
    This will not work because you cant use wildcard "\*" when crendentials mode is 'include'. The solution I found is to substitute "\*" for request.getHeader("Origin") when setting "Access-Control-Allow-Origin" header. This worked for me. – CleitonCardoso Aug 21 '19 at 15:59
  • 2
    For anyone who happens by and gets a little bit lost like I did, the `Filter` I implemented was the `javax.servlet.Filter`. Worked like a charm. Might seem obvious, but hopefully this saves someone the Google search time I spent figuring it out. – Francis Bartkowiak Apr 22 '20 at 03:23
33

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()...
  }
}
Ikechukwu Eze
  • 2,703
  • 1
  • 13
  • 18
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

sisanared
  • 4,175
  • 2
  • 27
  • 42
4

I just had a similar issue, I was trying to execute a request from my frontend in React executing on http://localhost:3000, to my backend in SpringBoot executing at http://localhost:8080. I had two errors:

Access Control Allow Origin

I solved this very easily by adding this to my RestController:

@CrossOrigin(origins = ["http://localhost:3000"])

After fixing this, I started getting this error: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true'

Access-Control-Allow-Credentials

This one can be worked around in two ways:

  1. Adding allowCredentials = "true" to the CrossOrigin configuration:

    @CrossOrigin(origins = ["http://localhost:3000"], allowCredentials = "true")

  2. Changing the credential options of the fetch in the frontend request. Basically, you'll need to perform the fetch call like this:

    fetch('http://localhost:8080/your/api', { credentials: 'same-origin' })

Hope this helps =)

3

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

Dennis Kieselhorst
  • 1,280
  • 1
  • 13
  • 23
3

If you need it for quick local development just add this annotation on your controller. (offcourse change origins as required)

@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600)
Neeraj
  • 1,163
  • 2
  • 18
  • 32
2

You could also achieve this with an interceptor.

Use the exception to ensure you are ending the lifecycle of the request:

@ResponseStatus (
    value = HttpStatus.NO_CONTENT
)
public class CorsException extends RuntimeException
{
}

Then, in your interceptor, set headers for all OPTIONS requests and throw the exception:

public class CorsMiddleware extends HandlerInterceptorAdapter
{
    @Override
    public boolean preHandle (
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler
    ) throws Exception
    {
        if (request.getMethod().equals("OPTIONS")) {
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Access-Control-Allow-Credentials", "true");
            response.addHeader("Access-Control-Allow-Methods","GET, POST, PUT, OPTIONS, DELETE");
            response.addHeader("Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,Authorization,If-Modified-Since,Cache-Control,Content-Type");
            response.addHeader("Access-Control-Max-Age", "3600");
            response.addHeader("charset", "utf-8");
            throw new CorsException();
        }

        return super.preHandle(request, response, handler);
    }
}

Lastly, apply the interceptor to all routes:

@Configuration
public class MiddlewareConfig extends WebMvcConfigurerAdapter
{
    @Override
    public void addInterceptors (InterceptorRegistry registry)
    {
        registry.addInterceptor(new CorsMiddleware())
                .addPathPatterns("/**");
    }
}
1

If anyone struggles with the same problem in 2023. here's the solution: the problem is that when working with SecurityConfig Class we should configure our security rules to allow the necessary CORS requests, so if you don't the cors checks will not bypassed.Even you configure a CorsConfig class that implements WebMvcConfigurer or using @CrossOrigin annotation in your Controller or adding a method with @Bean annotation in your ApplicationClass that allow cross-origin requests.In otherwise if we use permitAll() in our security rules it bypasses the security checks, including CORS checks . So To fix the issue we need to configure our security rules to allow the necessary CORS requests.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            .csrf((csrf) -> csrf.disable())
            .cors(cors -> {
                cors.configurationSource(corsConfigurationSource());
            })
            .authorizeHttpRequests((auth) -> auth
                    .anyRequest()
                    .authenticated()
            )
            .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));

    return httpSecurity.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.addAllowedOrigin("http://localhost:3000"); 
    configuration.addAllowedMethod("*"); 
    configuration.addAllowedHeader("*"); 
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new 
     UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}
}
0

If anyone struggles with the same problem in 2020. here's what did the work for me. This app is for learning purposes so I have enabled everything

CorsFilter class:

public class CorsFilter implements Filter {

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

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Content-Length, X-Requested-With");
        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {

    }
}

and then again setup of headers in class extending WebSecurityConfigurerAdapter:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {

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

    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("Im configuring it");
        (
                (HttpSecurity)
                        (
                                (HttpSecurity)
                                        (
                                                (ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)
                                                        http
                                                                .headers().addHeaderWriter(
                                                                new StaticHeadersWriter("Access-Control-Allow-Origin", "*")).and()
                                                                .addFilterBefore(corsFilter(), SessionManagementFilter.class)
                                                                .csrf().disable()
                                                                .authorizeRequests()
                                                                .antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
                                                                .anyRequest()

                                        ).authenticated().and()
                        ).formLogin().and()
        ).httpBasic();
    }

}
Dach0
  • 309
  • 4
  • 11
0

I tried with below config and it worked!

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().cors().configurationSource(configurationSource()).and()
        .requiresChannel()
        .anyRequest()
        .requiresSecure();   
}


private CorsConfigurationSource configurationSource() {
      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      CorsConfiguration config = new CorsConfiguration();
      config.addAllowedOrigin("*");
      config.setAllowCredentials(true);
      config.addAllowedHeader("X-Requested-With");
      config.addAllowedHeader("Content-Type");
      config.addAllowedMethod(HttpMethod.POST);
      source.registerCorsConfiguration("/**", config);
      return source;
    }
 }
0

What work for me:

Step 1 - Implement a ConfigurationClass

package med.voll.api.infra.security;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfigCors implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*") // somente do servidor na porta 3000
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT"); // métodos
                                                                                                        // permitidos;
    }
}

Step 2 - Enable Cors in my SecurityConfigurations class

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .cors().and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeHttpRequests()
                .requestMatchers(HttpMethod.GET, "/hello").permitAll()
                .requestMatchers(HttpMethod.POST, "/login").permitAll()
                // .requestMatchers(HttpMethod.GET, "/hello").permitAll()
                .anyRequest().authenticated()
                .and().addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

See that in the first line of httpSecurity param, i put .cors().and()

And thats works fine!
0

If you are using Spring WebFlux, you should add the following method to your application class to enable CORS:

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowedOrigins(Arrays.asList("*"));
        corsConfig.setMaxAge(3600L);
        corsConfig.addAllowedMethod("*");
        corsConfig.addAllowedHeader("Requestor-Type");
        corsConfig.addExposedHeader("X-Get-Header");

        UrlBasedCorsConfigurationSource source =
                new UrlBasedCorsConfigurationSource();

        source.registerCorsConfiguration("/**", corsConfig);

        return new CorsWebFilter(source);
    }
Allan Juan
  • 2,048
  • 1
  • 18
  • 42