user1935987
user1935987

Reputation: 3347

Spring Boot Security CORS

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

Upvotes: 48

Views: 186467

Answers (13)

tarek oussabi
tarek oussabi

Reputation: 61

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

Upvotes: 4

Allan Juan
Allan Juan

Reputation: 2554

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

Upvotes: 1

Crystyan S. Santos
Crystyan S. Santos

Reputation: 61

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!

Upvotes: 1

user15475178
user15475178

Reputation: 1

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

Upvotes: 0

Ikechukwu Eze
Ikechukwu Eze

Reputation: 3131

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

Upvotes: 38

Dach0
Dach0

Reputation: 337

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

}

Upvotes: 1

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 =)

Upvotes: 4

Neeraj
Neeraj

Reputation: 1173

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)

Upvotes: 3

Jason Fitzpatrick
Jason Fitzpatrick

Reputation: 21

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

Upvotes: 2

Dennis Kieselhorst
Dennis Kieselhorst

Reputation: 1387

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

Upvotes: 3

sisanared
sisanared

Reputation: 4287

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

Upvotes: 4

The Gilbert Arenas Dagger
The Gilbert Arenas Dagger

Reputation: 12741

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

Upvotes: 66

mika
mika

Reputation: 2585

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

Upvotes: 50

Related Questions