jqueryspringtomcatspring-securitycors

CORS issue - No 'Access-Control-Allow-Origin' header is present on the requested resource


I have created two web applications - client and service apps.
The interaction between client and service apps goes fine when they are deployed in same Tomcat instance.
But when the apps are deployed into seperate Tomcat instances (different machines), I get the below error when request to sent service app.

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://localhost:8080' is therefore not allowed access. The response had HTTP status code 401

My Client application uses JQuery, HTML5 and Bootstrap.

AJAX call is made to service as shown below:

var auth = "Basic " + btoa({usname} + ":" + {password});
var service_url = {serviceAppDomainName}/services;

if($("#registrationForm").valid()){
    var formData = JSON.stringify(getFormData(registrationForm));
    $.ajax({
        url: service_url+action,
        dataType: 'json',
        async: false,
        type: 'POST',
        headers:{
            "Authorization":auth
        },
        contentType: 'application/json',
        data: formData,
        success: function(data){
            //success code
        },
        error: function( jqXhr, textStatus, errorThrown ){
            alert( errorThrown );
        });
}

My service application uses Spring MVC, Spring Data JPA and Spring Security.

I have included CorsConfiguration class as shown below:

CORSConfig.java:

@Configuration
@EnableWebMvc
public class CORSConfig extends WebMvcConfigurerAdapter  {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("*");
    }
}

SecurityConfig.java:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@ComponentScan(basePackages = "com.services", scopedProxy = ScopedProxyMode.INTERFACES)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationService")
    private UserDetailsService userDetailsService;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().fullyAuthenticated();
        http.httpBasic();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.csrf().disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
}

Spring Security dependencies:

 <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>3.2.3.RELEASE</version>
</dependency>
<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>3.2.3.RELEASE</version>
</dependency>

I am using Apache Tomcat server for deployment.


Solution

  • CORS' preflight request uses HTTP OPTIONS without credentials, see Cross-Origin Resource Sharing:

    Otherwise, make a preflight request. Fetch the request URL from origin source origin using referrer source as override referrer source with the manual redirect flag and the block cookies flag set, using the method OPTIONS, and with the following additional constraints:

    • Include an Access-Control-Request-Method header with as header field value the request method (even when that is a simple method).
    • If author request headers is not empty include an Access-Control-Request-Headers header with as header field value a comma-separated list of the header field names from author request headers in lexicographical order, each converted to ASCII lowercase (even when one or more are a simple header).
    • Exclude the author request headers.
    • Exclude user credentials.
    • Exclude the request entity body.

    You have to allow anonymous access for HTTP OPTIONS.

    Spring Security 3

    Your modified (and simplified) code:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http
           .authorizeRequests()
               .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
               .antMatchers("/login").permitAll()
               .anyRequest().fullyAuthenticated()
               .and()
           .httpBasic()
               .and()
           .sessionManagement()
               .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
           .csrf().disable();
    }
    

    You still need your CORS configuration (probably with some additional values):

    @Configuration
    @EnableWebMvc
    public class CORSConfig extends WebMvcConfigurerAdapter  {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("*");
        }
    }
    

    Spring Security 4

    Since Spring Security 4.2.0 you can use the built-in support, see Spring Security Reference:

    19. CORS

    Spring Framework provides first class support for CORS. CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.

    The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following:

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

    Spring Security 5/6

    For Spring Security 5/6 see Spring Security Reference:

    CORS

    Spring Framework provides first class support for CORS. CORS must be processed before Spring Security, because the pre-flight request does not contain any cookies (that is, the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request determines that the user is not authenticated (since there are no cookies in the request) and rejects it.

    The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource that uses the following:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig {
    
      @Bean
      public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
          http
              // by default uses a Bean by the name of corsConfigurationSource
              .cors(withDefaults())
              ...
          return http.build();
      }
    
      @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;
      }
    }
    

    With Spring Security 5/6 you don't need to configure CORS twice (Spring Security and Spring MVC):

    If you use Spring MVC’s CORS support, you can omit specifying the CorsConfigurationSource and Spring Security uses the CORS configuration provided to Spring MVC: