diff --git a/src/main/java/org/fairdatateam/fairdatapoint/api/filter/CORSFilter.java b/src/main/java/org/fairdatateam/fairdatapoint/api/filter/CORSFilter.java
deleted file mode 100644
index 53a0ebe99..000000000
--- a/src/main/java/org/fairdatateam/fairdatapoint/api/filter/CORSFilter.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * The MIT License
- * Copyright © 2017 FAIR Data Team
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
-package org.fairdatateam.fairdatapoint.api.filter;
-
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.springframework.http.HttpHeaders;
-import org.springframework.stereotype.Component;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.filter.OncePerRequestFilter;
-
-import java.io.IOException;
-
-import static java.lang.String.format;
-
-@Component
-public class CORSFilter extends OncePerRequestFilter {
-
- @Override
- public void doFilterInternal(
- final HttpServletRequest request,
- final HttpServletResponse response,
- final FilterChain fc
- ) throws IOException, ServletException {
- final String allowedMtds = String.join(",",
- RequestMethod.GET.name(), RequestMethod.POST.name(),
- RequestMethod.PUT.name(), RequestMethod.PATCH.name(),
- RequestMethod.DELETE.name());
-
- response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
- response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
- format("%s,%s,%s,%s", HttpHeaders.ORIGIN, HttpHeaders.AUTHORIZATION,
- HttpHeaders.ACCEPT, HttpHeaders.CONTENT_TYPE));
- response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
- format("%s,%s", HttpHeaders.LOCATION, HttpHeaders.LINK));
- response.setHeader(HttpHeaders.ALLOW, allowedMtds);
- response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowedMtds);
-
- fc.doFilter(request, response);
- }
-}
diff --git a/src/main/java/org/fairdatateam/fairdatapoint/api/filter/FilterConfigurer.java b/src/main/java/org/fairdatateam/fairdatapoint/api/filter/FilterConfigurer.java
index b80a88fa8..8c32e9eb1 100644
--- a/src/main/java/org/fairdatateam/fairdatapoint/api/filter/FilterConfigurer.java
+++ b/src/main/java/org/fairdatateam/fairdatapoint/api/filter/FilterConfigurer.java
@@ -36,17 +36,13 @@ public class FilterConfigurer extends
@Autowired
private JwtTokenFilter jwtTokenFilter;
- @Autowired
- private CORSFilter corsFilter;
-
@Autowired
private LoggingFilter loggingFilter;
@Override
public void configure(HttpSecurity http) {
http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
- http.addFilterBefore(corsFilter, JwtTokenFilter.class);
- http.addFilterBefore(loggingFilter, CORSFilter.class);
+ http.addFilterBefore(loggingFilter, JwtTokenFilter.class);
}
}
diff --git a/src/main/java/org/fairdatateam/fairdatapoint/config/SecurityConfig.java b/src/main/java/org/fairdatateam/fairdatapoint/config/SecurityConfig.java
index e7fabe00d..bf923fd75 100644
--- a/src/main/java/org/fairdatateam/fairdatapoint/config/SecurityConfig.java
+++ b/src/main/java/org/fairdatateam/fairdatapoint/config/SecurityConfig.java
@@ -28,6 +28,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -35,6 +36,7 @@
import org.springframework.security.web.SecurityFilterChain;
@Configuration
+// Spring Security docs use @EnableWebSecurity, but we don't need it here because of Spring Boot autoconfiguration.
public class SecurityConfig {
@Bean
@@ -67,6 +69,11 @@ public SecurityFilterChain filterChain(HttpSecurity http, FilterConfigurer filte
.anyRequest().permitAll();
}
)
+ // Enable CORS for the security filter chain.
+ // "If Spring MVC is on classpath and no CorsConfigurationSource is provided,
+ // Spring Security will use CORS configuration provided to Spring MVC."
+ // https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html#cors-spring-mvc-integration
+ .cors(Customizer.withDefaults())
.apply(filterConfigurer);
return http.build();
}
diff --git a/src/main/java/org/fairdatateam/fairdatapoint/config/WebMvcConfig.java b/src/main/java/org/fairdatateam/fairdatapoint/config/WebMvcConfig.java
index 540c31b7f..7319dda16 100644
--- a/src/main/java/org/fairdatateam/fairdatapoint/config/WebMvcConfig.java
+++ b/src/main/java/org/fairdatateam/fairdatapoint/config/WebMvcConfig.java
@@ -30,11 +30,13 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@@ -85,4 +87,24 @@ public ObjectMapper objectMapper() {
public InternalResourceViewResolver defaultViewResolver() {
return new InternalResourceViewResolver();
}
+
+ /**
+ * Global Cross-Origin Resource Sharing (CORS) configuration based on
+ *
+ * Web MVC CORS example
+ */
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**")
+ .allowedOrigins("*")
+ .allowedMethods("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE")
+ .allowedHeaders(
+ HttpHeaders.ORIGIN,
+ HttpHeaders.AUTHORIZATION,
+ HttpHeaders.ACCEPT,
+ HttpHeaders.CONTENT_TYPE
+ )
+ // If the response contains these headers, the browser will expose them in JavaScript
+ .exposedHeaders(HttpHeaders.LOCATION, HttpHeaders.LINK);
+ }
}
diff --git a/src/test/java/org/fairdatateam/fairdatapoint/acceptance/general/CorsTest.java b/src/test/java/org/fairdatateam/fairdatapoint/acceptance/general/CorsTest.java
new file mode 100644
index 000000000..ca4ba5905
--- /dev/null
+++ b/src/test/java/org/fairdatateam/fairdatapoint/acceptance/general/CorsTest.java
@@ -0,0 +1,108 @@
+/**
+ * The MIT License
+ * Copyright © 2017 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatateam.fairdatapoint.acceptance.general;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.fairdatateam.fairdatapoint.Profiles;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.assertj.MockMvcTester;
+import org.springframework.test.web.servlet.assertj.MvcTestResult;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies Cross-Origin Resource Sharing (CORS) configuration. For more information see
+ * spring security CORS
+ * and spring web mvc CORS.
+ */
+@ActiveProfiles(Profiles.TESTING)
+@AutoConfigureMockMvc
+@SpringBootTest
+public class CorsTest {
+
+ private final String otherOrigin = "https://other.origin";
+ private final String uri = "/";
+
+ private final MockMvcTester mockMvc;
+
+ // todo: do we need to enable the security filter chain as well, to test http.cors()?
+ // https://docs.spring.io/spring-security/reference/servlet/test/mockmvc/setup.html
+
+ /**
+ * Constructor
+ */
+ @Autowired
+ public CorsTest(MockMvcTester mockMvc) {
+ this.mockMvc = mockMvc;
+ }
+
+ @Test
+ public void normalRequestYieldsNoAccessControlHeaders() {
+ // request without Origin header
+ MvcTestResult testResult = mockMvc.options().uri(uri).exchange();
+ assertThat(testResult).hasStatusOk()
+ .doesNotContainHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN);
+ }
+
+ @Test
+ public void corsPreflightRequestFailsIfAccessControlRequestMethodHeaderMissing() {
+ // request with Origin header, but without Access-Control-Request-Method header
+ MvcTestResult testResult = mockMvc.options()
+ .uri(uri)
+ .header(HttpHeaders.ORIGIN, otherOrigin)
+ .exchange();
+ assertThat(testResult).hasStatus(HttpStatus.FORBIDDEN)
+ .hasBodyTextEqualTo("Invalid CORS request");
+ }
+
+ @Test
+ public void corsPreflightRequestYieldsExpectedAccessControlHeaders() {
+ // valid CORS request, with both required headers
+ MvcTestResult testResult = mockMvc.options()
+ .uri(uri)
+ // this preflight says: "I plan to make a GET request with this header from this origin."
+ .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.GET)
+ .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, HttpHeaders.AUTHORIZATION)
+ .header(HttpHeaders.ORIGIN, otherOrigin)
+ .exchange();
+ List.of(
+ // note that Access-Control-Allow-Headers is only returned if the request
+ // contains Access-Control-Request-Headers
+ HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
+ HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
+ HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
+ HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS
+ ).forEach(header -> assertThat(testResult).hasStatusOk().containsHeader(header));
+
+ }
+
+}