Spring Data Rest and Cors

JavaSpringSpring MvcCorsSpring Data-Rest

Java Problem Overview


I am developing a Spring Boot application with a Rest interface and a dart fronted.

The XMLHttpRequest does execute a OPTIONS request which is handled totally correct. After this, the final GET ("/products") request is issued and fails:

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:63343'; is therefore not allowed access.

After some debugging I have found the following: The AbstractHandlerMapping.corsConfiguration is populated for all Subclasses except RepositoryRestHandlerMapping. In the RepositoryRestHandlerMapping no corsConfiguration is present / set at creation time and so it won't get recognized as cors path / resource.
=> No CORS headers attached
Could that be the problem? How can I set it?

Configuration classes:

@Configuration
public class RestConfiguration extends RepositoryRestMvcConfiguration {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowCredentials(false).allowedOrigins("*").allowedMethods("PUT", "POST", "GET", "OPTIONS", "DELETE").exposedHeaders("Authorization", "Content-Type");
    }
       
   ...
}

I even tried to set the Cors per annotation:

@CrossOrigin( methods = RequestMethod.GET, allowCredentials = "false")
public interface ProductRepository extends CrudRepository<Product, String> {


}

Raw request headers:

GET /products HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
authorization: Basic dXNlcjpwYXNzd29yZA==
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/43.0.2357.130 Chrome/43.0.2357.130 Safari/537.36
Content-Type: application/json
Accept: */*
Referer: http://localhost:63343/inventory-web/web/index.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4

Raw response headers:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 30 Jul 2015 15:58:03 GMT

Versions used: Spring Boot 1.3.0.M2 Spring 4.2.0.RC2

What do I miss?

Java Solutions


Solution 1 - Java

Indeed, before Spring Data REST 2.6 (Ingalls) only HandlerMapping instances created by Spring MVC WebMvcConfigurationSupport and controllers annotated with @CrossOrigin were CORS aware.

But now that DATAREST-573 has been fixed, RepositoryRestConfiguration now exposes a getCorsRegistry() for global setup and @CrossOrigin annotations on repositories are also recognized so this is the recommended approach. See https://stackoverflow.com/a/42403956/1092077 answer for concrete examples.

For people that have to stick to Spring Data REST 2.5 (Hopper) or previous versions, I think the best solution is to use a filter based approach. You could obviously use Tomcat, Jetty or this one, but be aware that Spring Framework 4.2 also provides a CorsFilter that use the same CORS processing logic that @CrossOrigin and addCorsMappings(CorsRegistry registry) approaches. By passing an UrlBasedCorsConfigurationSource instance to the CorsFilter constructor parameter, you could easily get something as powerful as Spring native CORS global support.

If you are using Spring Boot (which supports Filter beans), it could be something like:

@Configuration
public class RestConfiguration {

	@Bean
	public FilterRegistrationBean corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
		source.registerCorsConfiguration("/**", config);
		FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
    	bean.setOrder(0);
    	return bean;
	}
}

Solution 2 - Java

Since the Ingalls train has been realised, the support of CORS in Spring Data is now on. There are two ways to deal with:

  1. The @CrossOrigin annotation with specifying origins, methods, and allowedHeaders over a @RepositoryRestResource interface.

     @CrossOrigin(...)
     @RepositoryRestResource
     public interface PageRepository extends CrudRepository<Page, Long> { ... }
    
  2. A global configuration with the RepositoryRestConfiguration inside a @Configuration class. Marking repositories by the @CrossOrigin is not necessary then.

     @Configuration
     public class GlobalRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {
    
         @Override
         public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
             config.getCorsRegistry()
                       .addMapping(CORS_BASE_PATTERN)
                       .allowedOrigins(ALLOWED_ORIGINS)
                       .allowedHeaders(ALLOWED_HEADERS)
                       .allowedMethods(ALLOWED_METHODS);
          }
    
     }
    

Solution 3 - Java

For some reason the approach suggested in the accepted answer above didn't work for me after upgrading from Spring Boot 1.5.2 to 1.5.6.

As also pointed out by @BigDong's comment, the exception I got was:

> BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeExcep‌​tion: Bean named 'corsFilter' is expected to be of type 'org.springframework.web.filter.CorsFilter' but was actually of type 'org.springframework.boot.web.servlet.FilterRegistrationBean

So here's what I came up with to get a "global" CORS configuration for all endpoint in our REST API, whether they're implemented using Spring Data Rest or Spring MVC, with all endpoints protected by Spring Security.

I wasn't able to hook a CorsFilter into the request pipeline at the right point, so instead I configured SDR and MVC separately, however using the same configuration for their CorsRegistry via this helper:

public static void applyFullCorsAllowedPolicy(CorsRegistry registry) {
    registry.addMapping("/**") //
            .allowedOrigins("*") //
            .allowedMethods("OPTIONS", "HEAD", "GET", "PUT", "POST", "DELETE", "PATCH") //
            .allowedHeaders("*") //
            .exposedHeaders("WWW-Authenticate") //
            .allowCredentials(true)
            .maxAge(TimeUnit.DAYS.toSeconds(1));
}

And then for MVC:

@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CustomWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // enables CORS as per
        // https://docs.spring.io/spring-security/site/docs/current/reference/html/cors.html#cors
        http.cors()
            .and() // ...
    }

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                applyFullCorsAllowedPolicy(registry);
            }
        };
    }
}

And then for SDR:

public class CustomRepositoryRestMvcConfiguration extends RepositoryRestConfigurerAdapter {

@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.setReturnBodyOnCreate(true);
    config.setReturnBodyForPutAndPost(true);
    config.setReturnBodyOnUpdate(true);
    config.setMaxPageSize(250);
    config.setDefaultPageSize(50);
    config.setDefaultMediaType(MediaTypes.HAL_JSON);
    config.useHalAsDefaultJsonMediaType(true);

    CustomWebSecurityConfiguration.applyFullCorsAllowedPolicy(config.getCorsRegistry());
}

Here's some further reference on the subject that helped me come up with this answer:

Solution 4 - Java

RepositoryRestConfigurerAdapter is deprecated from 3.1 so if you used spring boot and data-rest version 3.1 above you can directly implement RepositoryRestConfigurer :

@Configuration
public class ConfigRepositoryRest implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.getCorsRegistry()
            .addMapping("/**")
            .allowedOrigins("http://localhost:3000");
}

}

Solution 5 - Java

I was trying to hitting spring rest service from angular. Spring rest project deployed in tomcat server and angular is default angular server. I faced this issue when it is hitting from angular to service. I tried to follow

https://juristr.com/blog/2016/11/configure-proxy-api-angular-cli/

but problem still there. Thanks to my senior 'Abbas bhai', he suggested please add some configuration in spring configuration file to avoid this problem, so I added this code in spring configuration.

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@ComponentScan("org.liferayasif.backend")
public class RestConfig extends WebMvcConfigurerAdapter{

	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
		configurer.enable();
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addCorsMappings(org.springframework.web.servlet.config.annotation.CorsRegistry)
	 * To avoid 'Access-Control-Allow-Origin'
	 * Above error arising when I am hitting from angular to our rest service
	 */
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**");
	}
	
}

This method resolved my 'Access-Control-Allow-Origin' cors.

@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**");
	}

For reference can download my whole project

My github url link:

https://github.com/asifaftab87/SpringPersistenceHibernate

Branch - security

Project - Model

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThomas LetschView Question on Stackoverflow
Solution 1 - JavaSébastien DeleuzeView Answer on Stackoverflow
Solution 2 - JavaAndrew TobilkoView Answer on Stackoverflow
Solution 3 - JavaJohannes RudolphView Answer on Stackoverflow
Solution 4 - JavaFazel FarniaView Answer on Stackoverflow
Solution 5 - Javaasifaftab87View Answer on Stackoverflow