Skip to content

Commit 6703e58

Browse files
authored
Update CORS configuration (#930)
* add CorsTest class * replace custom CORSFilter implementation by WebMvcConfigurer.addCorsMappings() settings are equivalent to the original custom CORSFilter.java implementation, but now we also allow the HEAD method, for completeness new implementation based on docs: https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html#mvc-cors-global * enable CORS for the security filter chain (this is *in addition to* the web-mvc CORS handling, and uses the web mvc cors config)
1 parent 780172a commit 6703e58

5 files changed

Lines changed: 138 additions & 73 deletions

File tree

src/main/java/org/fairdatateam/fairdatapoint/api/filter/CORSFilter.java

Lines changed: 0 additions & 68 deletions
This file was deleted.

src/main/java/org/fairdatateam/fairdatapoint/api/filter/FilterConfigurer.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,13 @@ public class FilterConfigurer extends
3636
@Autowired
3737
private JwtTokenFilter jwtTokenFilter;
3838

39-
@Autowired
40-
private CORSFilter corsFilter;
41-
4239
@Autowired
4340
private LoggingFilter loggingFilter;
4441

4542
@Override
4643
public void configure(HttpSecurity http) {
4744
http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
48-
http.addFilterBefore(corsFilter, JwtTokenFilter.class);
49-
http.addFilterBefore(loggingFilter, CORSFilter.class);
45+
http.addFilterBefore(loggingFilter, JwtTokenFilter.class);
5046
}
5147

5248
}

src/main/java/org/fairdatateam/fairdatapoint/config/SecurityConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
import org.springframework.context.annotation.Configuration;
2929
import org.springframework.http.HttpMethod;
3030
import org.springframework.security.authentication.AuthenticationManager;
31+
import org.springframework.security.config.Customizer;
3132
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
3233
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3334
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
3435
import org.springframework.security.config.http.SessionCreationPolicy;
3536
import org.springframework.security.web.SecurityFilterChain;
3637

3738
@Configuration
39+
// Spring Security docs use @EnableWebSecurity, but we don't need it here because of Spring Boot autoconfiguration.
3840
public class SecurityConfig {
3941

4042
@Bean
@@ -67,6 +69,11 @@ public SecurityFilterChain filterChain(HttpSecurity http, FilterConfigurer filte
6769
.anyRequest().permitAll();
6870
}
6971
)
72+
// Enable CORS for the security filter chain.
73+
// "If Spring MVC is on classpath and no CorsConfigurationSource is provided,
74+
// Spring Security will use CORS configuration provided to Spring MVC."
75+
// https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html#cors-spring-mvc-integration
76+
.cors(Customizer.withDefaults())
7077
.apply(filterConfigurer);
7178
return http.build();
7279
}

src/main/java/org/fairdatateam/fairdatapoint/config/WebMvcConfig.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@
3030
import org.springframework.context.annotation.Bean;
3131
import org.springframework.context.annotation.Configuration;
3232
import org.springframework.context.annotation.Primary;
33+
import org.springframework.http.HttpHeaders;
3334
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
3435
import org.springframework.http.converter.HttpMessageConverter;
3536
import org.springframework.http.converter.StringHttpMessageConverter;
3637
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
3738
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
39+
import org.springframework.web.servlet.config.annotation.CorsRegistry;
3840
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
3941
import org.springframework.web.servlet.view.InternalResourceViewResolver;
4042

@@ -85,4 +87,24 @@ public ObjectMapper objectMapper() {
8587
public InternalResourceViewResolver defaultViewResolver() {
8688
return new InternalResourceViewResolver();
8789
}
90+
91+
/**
92+
* Global Cross-Origin Resource Sharing (CORS) configuration based on
93+
* <a href="https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html#mvc-cors-global">
94+
* Web MVC CORS example</a>
95+
*/
96+
@Override
97+
public void addCorsMappings(CorsRegistry registry) {
98+
registry.addMapping("/**")
99+
.allowedOrigins("*")
100+
.allowedMethods("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE")
101+
.allowedHeaders(
102+
HttpHeaders.ORIGIN,
103+
HttpHeaders.AUTHORIZATION,
104+
HttpHeaders.ACCEPT,
105+
HttpHeaders.CONTENT_TYPE
106+
)
107+
// If the response contains these headers, the browser will expose them in JavaScript
108+
.exposedHeaders(HttpHeaders.LOCATION, HttpHeaders.LINK);
109+
}
88110
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* The MIT License
3+
* Copyright © 2017 FAIR Data Team
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
package org.fairdatateam.fairdatapoint.acceptance.general;
24+
25+
import org.eclipse.jetty.http.HttpHeader;
26+
import org.fairdatateam.fairdatapoint.Profiles;
27+
import org.junit.jupiter.api.Test;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
30+
import org.springframework.boot.test.context.SpringBootTest;
31+
import org.springframework.http.HttpHeaders;
32+
import org.springframework.http.HttpMethod;
33+
import org.springframework.http.HttpStatus;
34+
import org.springframework.test.context.ActiveProfiles;
35+
import org.springframework.test.web.servlet.assertj.MockMvcTester;
36+
import org.springframework.test.web.servlet.assertj.MvcTestResult;
37+
38+
import java.util.List;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
42+
/**
43+
* Verifies Cross-Origin Resource Sharing (CORS) configuration. For more information see
44+
* <a href="https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html">spring security CORS</a>
45+
* and <a href="https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html">spring web mvc CORS</a>.
46+
*/
47+
@ActiveProfiles(Profiles.TESTING)
48+
@AutoConfigureMockMvc
49+
@SpringBootTest
50+
public class CorsTest {
51+
52+
private final String otherOrigin = "https://other.origin";
53+
private final String uri = "/";
54+
55+
private final MockMvcTester mockMvc;
56+
57+
// todo: do we need to enable the security filter chain as well, to test http.cors()?
58+
// https://docs.spring.io/spring-security/reference/servlet/test/mockmvc/setup.html
59+
60+
/**
61+
* Constructor
62+
*/
63+
@Autowired
64+
public CorsTest(MockMvcTester mockMvc) {
65+
this.mockMvc = mockMvc;
66+
}
67+
68+
@Test
69+
public void normalRequestYieldsNoAccessControlHeaders() {
70+
// request without Origin header
71+
MvcTestResult testResult = mockMvc.options().uri(uri).exchange();
72+
assertThat(testResult).hasStatusOk()
73+
.doesNotContainHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN);
74+
}
75+
76+
@Test
77+
public void corsPreflightRequestFailsIfAccessControlRequestMethodHeaderMissing() {
78+
// request with Origin header, but without Access-Control-Request-Method header
79+
MvcTestResult testResult = mockMvc.options()
80+
.uri(uri)
81+
.header(HttpHeaders.ORIGIN, otherOrigin)
82+
.exchange();
83+
assertThat(testResult).hasStatus(HttpStatus.FORBIDDEN)
84+
.hasBodyTextEqualTo("Invalid CORS request");
85+
}
86+
87+
@Test
88+
public void corsPreflightRequestYieldsExpectedAccessControlHeaders() {
89+
// valid CORS request, with both required headers
90+
MvcTestResult testResult = mockMvc.options()
91+
.uri(uri)
92+
// this preflight says: "I plan to make a GET request with this header from this origin."
93+
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.GET)
94+
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, HttpHeaders.AUTHORIZATION)
95+
.header(HttpHeaders.ORIGIN, otherOrigin)
96+
.exchange();
97+
List.of(
98+
// note that Access-Control-Allow-Headers is only returned if the request
99+
// contains Access-Control-Request-Headers
100+
HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
101+
HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
102+
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
103+
HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS
104+
).forEach(header -> assertThat(testResult).hasStatusOk().containsHeader(header));
105+
106+
}
107+
108+
}

0 commit comments

Comments
 (0)