Spring Data Rest and Cors
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;
}
}
Since the Ingalls train has been realised, the support of CORS in Spring Data is now on. There are two ways to deal with:
-
The
@CrossOrigin
annotation with specifyingorigins
,methods
, andallowedHeaders
over a@RepositoryRestResource
interface.@CrossOrigin(...) @RepositoryRestResource public interface PageRepository extends CrudRepository<Page, Long> { ... }
-
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); } }
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.BeanNotOfRequiredTypeException: 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:
- https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
- https://docs.spring.io/spring-security/site/docs/current/reference/html/cors.html
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");
}
}