Skip to content

Commit 6188474

Browse files
Automatically enable .cors() if CorsConfigurationSource bean is present
Closes gh-5011
1 parent 52e12ad commit 6188474

File tree

4 files changed

+134
-47
lines changed

4 files changed

+134
-47
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@
4747
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
4848
import org.springframework.web.accept.ContentNegotiationStrategy;
4949
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
50+
import org.springframework.web.cors.CorsConfigurationSource;
5051

5152
import static org.springframework.security.config.Customizer.withDefaults;
5253

@@ -124,10 +125,18 @@ HttpSecurity httpSecurity() throws Exception {
124125
.apply(new DefaultLoginPageConfigurer<>());
125126
http.logout(withDefaults());
126127
// @formatter:on
128+
applyCorsIfAvailable(http);
127129
applyDefaultConfigurers(http);
128130
return http;
129131
}
130132

133+
private void applyCorsIfAvailable(HttpSecurity http) throws Exception {
134+
String[] beanNames = this.context.getBeanNamesForType(CorsConfigurationSource.class);
135+
if (beanNames.length == 1) {
136+
http.cors(withDefaults());
137+
}
138+
}
139+
131140
private AuthenticationManager authenticationManager() throws Exception {
132141
return this.authenticationConfiguration.getAuthenticationManager();
133142
}

config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -66,13 +66,18 @@
6666
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
6767
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
6868
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
69+
import org.springframework.test.util.ReflectionTestUtils;
6970
import org.springframework.test.web.servlet.MockMvc;
7071
import org.springframework.test.web.servlet.MvcResult;
7172
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
7273
import org.springframework.web.accept.ContentNegotiationStrategy;
7374
import org.springframework.web.bind.annotation.GetMapping;
7475
import org.springframework.web.bind.annotation.RestController;
7576
import org.springframework.web.context.request.NativeWebRequest;
77+
import org.springframework.web.cors.CorsConfiguration;
78+
import org.springframework.web.cors.CorsConfigurationSource;
79+
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
80+
import org.springframework.web.filter.CorsFilter;
7681

7782
import static org.assertj.core.api.Assertions.assertThat;
7883
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -350,6 +355,16 @@ public void disableConfigurerWhenAppliedByAnotherConfigurerThenNotApplied() {
350355
DefaultLogoutPageGeneratingFilter.class);
351356
}
352357

358+
@Test
359+
public void configureWhenCorsConfigurationSourceThenApplyCors() {
360+
this.spring.register(CorsConfigurationSourceConfig.class, DefaultWithFilterChainConfig.class).autowire();
361+
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
362+
CorsFilter corsFilter = (CorsFilter) filterChain.getFilters().stream().filter((f) -> f instanceof CorsFilter)
363+
.findFirst().get();
364+
Object configSource = ReflectionTestUtils.getField(corsFilter, "configSource");
365+
assertThat(configSource).isInstanceOf(UrlBasedCorsConfigurationSource.class);
366+
}
367+
353368
@RestController
354369
static class NameController {
355370

@@ -614,6 +629,20 @@ static CustomDsl customDsl() {
614629

615630
}
616631

632+
@Configuration
633+
static class CorsConfigurationSourceConfig {
634+
635+
@Bean
636+
CorsConfigurationSource corsConfigurationSource() {
637+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
638+
CorsConfiguration corsConfiguration = new CorsConfiguration();
639+
corsConfiguration.setAllowedOrigins(List.of("http://localhost:8080"));
640+
source.registerCorsConfiguration("/**", corsConfiguration);
641+
return source;
642+
}
643+
644+
}
645+
617646
static class DefaultConfigurer extends AbstractHttpConfigurer<DefaultConfigurer, HttpSecurity> {
618647

619648
boolean init;

docs/modules/ROOT/pages/servlet/integrations/cors.adoc

+91-45
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,38 @@ CORS must be processed before Spring Security, because the pre-flight request do
66
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.
77

88
The easiest way to ensure that CORS is handled first is to use the `CorsFilter`.
9-
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource` that uses the following:
9+
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource`.
10+
For example, the following will integrate CORS support within Spring Security:
1011

1112
[tabs]
1213
======
1314
Java::
1415
+
1516
[source,java,role="primary"]
1617
----
17-
@Configuration
18-
@EnableWebSecurity
19-
public class WebSecurityConfig {
20-
21-
@Bean
22-
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
23-
http
24-
// by default uses a Bean by the name of corsConfigurationSource
25-
.cors(withDefaults())
26-
...
27-
return http.build();
28-
}
29-
30-
@Bean
31-
CorsConfigurationSource corsConfigurationSource() {
32-
CorsConfiguration configuration = new CorsConfiguration();
33-
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
34-
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
35-
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
36-
source.registerCorsConfiguration("/**", configuration);
37-
return source;
38-
}
18+
@Bean
19+
CorsConfigurationSource corsConfigurationSource() {
20+
CorsConfiguration configuration = new CorsConfiguration();
21+
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
22+
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
23+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
24+
source.registerCorsConfiguration("/**", configuration);
25+
return source;
3926
}
4027
----
4128
4229
Kotlin::
4330
+
4431
[source,kotlin,role="secondary"]
4532
----
46-
@Configuration
47-
@EnableWebSecurity
48-
open class WebSecurityConfig {
49-
@Bean
50-
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
51-
http {
52-
// by default uses a Bean by the name of corsConfigurationSource
53-
cors { }
54-
// ...
55-
}
56-
return http.build()
57-
}
58-
59-
@Bean
60-
open fun corsConfigurationSource(): CorsConfigurationSource {
61-
val configuration = CorsConfiguration()
62-
configuration.allowedOrigins = listOf("https://example.com")
63-
configuration.allowedMethods = listOf("GET", "POST")
64-
val source = UrlBasedCorsConfigurationSource()
65-
source.registerCorsConfiguration("/**", configuration)
66-
return source
67-
}
33+
@Bean
34+
fun corsConfigurationSource(): CorsConfigurationSource {
35+
val configuration = CorsConfiguration()
36+
configuration.allowedOrigins = listOf("https://example.com")
37+
configuration.allowedMethods = listOf("GET", "POST")
38+
val source = UrlBasedCorsConfigurationSource()
39+
source.registerCorsConfiguration("/**", configuration)
40+
return source
6841
}
6942
----
7043
======
@@ -137,3 +110,76 @@ The following listing does the same thing in XML:
137110
...
138111
</http>
139112
----
113+
114+
If you have more than one `CorsConfigurationSource` bean, Spring Security won't automatically configure CORS support for you, that is because it cannot decide which one to use.
115+
If you want to specify different `CorsConfigurationSource` for each `SecurityFilterChain`, you can pass it directly into the `.cors()` DSL.
116+
117+
[tabs]
118+
======
119+
Java::
120+
+
121+
[source,java,role="primary"]
122+
----
123+
@Configuration
124+
@EnableWebSecurity
125+
public class WebSecurityConfig {
126+
127+
@Bean
128+
@Order(0)
129+
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
130+
http
131+
.securityMatcher("/api/**")
132+
.cors((cors) -> cors
133+
.configurationSource(apiConfigurationSource())
134+
)
135+
...
136+
return http.build();
137+
}
138+
139+
@Bean
140+
@Order(1)
141+
public SecurityFilterChain myOtherFilterChain(HttpSecurity http) throws Exception {
142+
http
143+
.cors((cors) -> cors
144+
.configurationSource(myWebsiteConfigurationSource())
145+
)
146+
...
147+
return http.build();
148+
}
149+
150+
CorsConfigurationSource apiConfigurationSource() {
151+
CorsConfiguration configuration = new CorsConfiguration();
152+
configuration.setAllowedOrigins(Arrays.asList("https://api.example.com"));
153+
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
154+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
155+
source.registerCorsConfiguration("/**", configuration);
156+
return source;
157+
}
158+
159+
CorsConfigurationSource myWebsiteConfigurationSource() {
160+
CorsConfiguration configuration = new CorsConfiguration();
161+
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
162+
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
163+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
164+
source.registerCorsConfiguration("/**", configuration);
165+
return source;
166+
}
167+
168+
}
169+
----
170+
171+
Kotlin::
172+
+
173+
[source,kotlin,role="secondary"]
174+
----
175+
@Bean
176+
fun corsConfigurationSource(): CorsConfigurationSource {
177+
val configuration = CorsConfiguration()
178+
configuration.allowedOrigins = listOf("https://example.com")
179+
configuration.allowedMethods = listOf("GET", "POST")
180+
val source = UrlBasedCorsConfigurationSource()
181+
source.registerCorsConfiguration("/**", configuration)
182+
return source
183+
}
184+
----
185+
======

docs/modules/ROOT/pages/whats-new.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
Spring Security 6.2 provides a number of new features.
55
Below are the highlights of the release.
66

7+
== Configuration
8+
9+
* https://github.com/spring-projects/spring-security/issues/5011[gh-5011] - xref:servlet/integrations/cors.adoc[(docs)] Automatically enable `.cors()` if `CorsConfigurationSource` bean is present

0 commit comments

Comments
 (0)