Spring Boot Security CORS
Asked Answered
H

13

47

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

Homocercal answered 27/10, 2016 at 13:56 Comment(0)
S
49

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')");
    }
}
Supercool answered 28/10, 2016 at 7:42 Comment(4)
seems like this was the key .addFilterBefore(corsFilter(), SessionManagementFilter.class) thanks. no tutorials pointing on it.Homocercal
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.Heathenry
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.Protraction
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.Cittern
A
64

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;
    }
}
Apo answered 4/11, 2016 at 21:31 Comment(6)
Seems the bean name must be 'corsConfigurationSource' or it is not picked up?Haha
I got error at cors(). 'The method cors() is undefined for the type HttpSecurity'Incarnate
@PawanTiwari use in this method signature protected void configure(HttpSecurity http) Dimorphous
@Haha 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.CorsConfigurerHeadwards
"You need to register CORS with your security configuration." This sentence saved my day.Photodrama
For those who used this solution (Option 2) and still had CORS errors in the browser add the following: configuration.setAllowedHeaders(Arrays.asList("*"));Iwo
S
49

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')");
    }
}
Supercool answered 28/10, 2016 at 7:42 Comment(4)
seems like this was the key .addFilterBefore(corsFilter(), SessionManagementFilter.class) thanks. no tutorials pointing on it.Homocercal
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.Heathenry
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.Protraction
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.Cittern
B
37

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()...
  }
}
Backstroke answered 1/4, 2020 at 1:6 Comment(7)
This should be the accepted answer as of December/2020 (using Spring Boot 2.3/2.4)Fairspoken
Is there a way to do this without lambda and place it as a Bean? because this and @Dach0 solution worked for me but this looks prettierCrouton
EDIT: creating Bean with CorsConfigurationSource works but requires cors.setAllowedHeadersCrouton
Thank you so much. Finally solved my problem. Stuck for around 3 weeks but none other solution worked!Gilliam
Thank you! But instead of you, i put .cors().configurationSource(...) at end of httpSecurity var. But you helped a lot!Tanked
short and crisp solution really. thanks!Arlberg
WebSecurityConfigurerAdapter is deprecated as of Spring Boot 2.7. It is recommended to use the SecurityFilter instead.Lyns
M
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

Metabolite answered 21/4, 2017 at 14:39 Comment(0)
H
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 =)

Harmonist answered 21/12, 2018 at 13:30 Comment(0)
D
4

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;
}
}
Dmitri answered 14/7, 2023 at 18:53 Comment(1)
Thank you for the explanation I was actually wondering about the sequence of events.Lyns
O
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

Oasis answered 10/7, 2017 at 12:37 Comment(0)
C
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)
Cabstand answered 3/6, 2018 at 14:38 Comment(0)
S
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("/**");
    }
}
Selfannihilation answered 5/10, 2017 at 13:3 Comment(0)
I
1

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

}
Irreproachable answered 31/1, 2020 at 13:2 Comment(0)
I
1

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!
Insurrectionary answered 3/3, 2023 at 23:19 Comment(0)
D
1

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);
    }
Deglutition answered 9/5, 2023 at 19:41 Comment(0)
I
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;
    }
 }
Ideology answered 25/3, 2021 at 4:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.