From 6fd515813c398ec5285dafd468f265be91696fe0 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 27 Jun 2019 15:07:41 -0400 Subject: [PATCH] Allow configuration of cors through nested builder Issue: gh-5557 --- .../annotation/web/builders/HttpSecurity.java | 30 ++++ .../web/configurers/CorsConfigurerTests.java | 150 ++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 4c0fae24e0..53af9b6e1e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -448,6 +448,36 @@ public final class HttpSecurity extends return getOrApply(new CorsConfigurer<>()); } + /** + * Adds a {@link CorsFilter} to be used. If a bean by the name of corsFilter is + * provided, that {@link CorsFilter} is used. Else if corsConfigurationSource is + * defined, then that {@link CorsConfiguration} is used. Otherwise, if Spring MVC is + * on the classpath a {@link HandlerMappingIntrospector} is used. + * You can enable CORS using: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CorsSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 *     protected void configure(HttpSecurity http) throws Exception {
+	 *         http
+	 *             .cors(withDefaults());
+	 *     }
+	 * }
+	 * 
+ * + * @param corsCustomizer the {@link Customizer} to provide more options for + * the {@link CorsConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity cors(Customizer> corsCustomizer) throws Exception { + corsCustomizer.customize(getOrApply(new CorsConfigurer<>())); + return HttpSecurity.this; + } + /** * Allows configuring of Session Management. * diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java index 8122817caf..b6bb33e58f 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.java @@ -42,6 +42,7 @@ import java.util.Arrays; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; @@ -131,6 +132,55 @@ public class CorsConfigurerTests { } } + @Test + public void getWhenDefaultsInLambdaAndCrossOriginAnnotationThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(MvcCorsInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @Test + public void optionsWhenDefaultsInLambdaAndCrossOriginAnnotationThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(MvcCorsInLambdaConfig.class).autowire(); + + this.mvc.perform(options("/") + .header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()) + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(status().isOk()) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @EnableWebMvc + @EnableWebSecurity + static class MvcCorsInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .cors(withDefaults()); + // @formatter:on + } + + @RestController + @CrossOrigin(methods = { + RequestMethod.GET, RequestMethod.POST + }) + static class CorsController { + @RequestMapping("/") + String hello() { + return "Hello"; + } + } + } + @Test public void getWhenCorsConfigurationSourceBeanThenRespondsWithCorsHeaders() throws Exception { this.spring.register(ConfigSourceConfig.class).autowire(); @@ -180,6 +230,57 @@ public class CorsConfigurerTests { } } + @Test + public void getWhenMvcCorsInLambdaConfigAndCorsConfigurationSourceBeanThenRespondsWithCorsHeaders() + throws Exception { + this.spring.register(ConfigSourceInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @Test + public void optionsWhenMvcCorsInLambdaConfigAndCorsConfigurationSourceBeanThenRespondsWithCorsHeaders() + throws Exception { + this.spring.register(ConfigSourceInLambdaConfig.class).autowire(); + + this.mvc.perform(options("/") + .header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()) + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(status().isOk()) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @EnableWebSecurity + static class ConfigSourceInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .cors(withDefaults()); + // @formatter:on + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.setAllowedOrigins(Collections.singletonList("*")); + corsConfiguration.setAllowedMethods(Arrays.asList( + RequestMethod.GET.name(), + RequestMethod.POST.name())); + source.registerCorsConfiguration("/**", corsConfiguration); + return source; + } + } + @Test public void getWhenCorsFilterBeanThenRespondsWithCorsHeaders() throws Exception { this.spring.register(CorsFilterConfig.class).autowire(); @@ -228,4 +329,53 @@ public class CorsConfigurerTests { return new CorsFilter(source); } } + + @Test + public void getWhenConfigSourceInLambdaConfigAndCorsFilterBeanThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(CorsFilterInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @Test + public void optionsWhenConfigSourceInLambdaConfigAndCorsFilterBeanThenRespondsWithCorsHeaders() throws Exception { + this.spring.register(CorsFilterInLambdaConfig.class).autowire(); + + this.mvc.perform(options("/") + .header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()) + .header(HttpHeaders.ORIGIN, "https://example.com")) + .andExpect(status().isOk()) + .andExpect(header().exists("Access-Control-Allow-Origin")) + .andExpect(header().exists("X-Content-Type-Options")); + } + + @EnableWebSecurity + static class CorsFilterInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .cors(withDefaults()); + // @formatter:on + } + + @Bean + CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.setAllowedOrigins(Collections.singletonList("*")); + corsConfiguration.setAllowedMethods(Arrays.asList( + RequestMethod.GET.name(), + RequestMethod.POST.name())); + source.registerCorsConfiguration("/**", corsConfiguration); + return new CorsFilter(source); + } + } }