This tutorial demonstrates five ways to configure Cross-Origin Resource Sharing (CORS) for OfficeFloor REST endpoints declared as YAML files:
composition: cors:, applies to the specific HTTP method served by that endpoint file@CrossOrigin placed on the service class, picked up automatically by OfficeFloor at start-upWebMvcConfigurer.addCorsMappings() applies path-pattern rules across all endpointsCorsConfigurationSource bean that OfficeFloor's handler interceptor picks up directly for OfficeFloor-managed endpoints<dependency> <groupId>net.officefloor.springboot</groupId> <artifactId>officefloor-rest-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
@SpringBootApplication
public class SpringRestCorsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringRestCorsApplication.class, args);
}
}
CORS rules can be placed directly in the endpoint YAML file under a top-level composition: cors: key. All fields of ComposeCorsConfiguration are available: allowed-origins, allowed-origin-patterns, allowed-methods, allowed-headers, exposed-headers, allow-credentials, allow-private-network, and max-age.
composition:
cors:
allowed-origins: [ "https://example.com" ]
service:
class: net.officefloor.tutorial.springrestcors.CompositionCorsService
The service class itself has no CORS knowledge — it just responds:
public class CompositionCorsService {
public void service(ObjectResponse<String> response) {
response.send("Hello from composition CORS endpoint");
}
}
@CrossOrigin annotation on the service classSpring's standard @CrossOrigin annotation placed on the service class is discovered by OfficeFloor at startup. It walks the composition graph of each endpoint and collects every @CrossOrigin it finds, merging the results.
@CrossOrigin(origins = "https://example.com")
public class AnnotationCorsService {
public void service(ObjectResponse<String> response) {
response.send("Hello from annotation CORS endpoint");
}
}
The YAML file needs no CORS configuration — the annotation is enough:
service: class: net.officefloor.tutorial.springrestcors.AnnotationCorsService
A YAML file without an HTTP method suffix (e.g. all-methods.yml rather than all-methods.GET.yml) acts as a shared composition that applies to every HTTP method exposed by that endpoint. Place CORS directly under the top-level cors: key:
cors: allowed-origins: [ "https://example.com" ]
Each per-method YAML file then only needs to declare its service class:
service: class: net.officefloor.tutorial.springrestcors.AllMethodsCorsService
public class AllMethodsCorsService {
public void service(ObjectResponse<String> response) {
response.send("Hello from all-methods CORS endpoint");
}
}
WebMvcConfigurerSpring's standard WebMvcConfigurer.addCorsMappings() mechanism works transparently with OfficeFloor-managed endpoints. Register a @Configuration class that implements WebMvcConfigurer and map the URL patterns that should be covered:
@Configuration
public class CorsMvcWebConfigurerConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/cors/mvc-configurer/**")
.allowedOrigins("https://example.com");
}
}
The endpoint YAML files need no CORS configuration — they only declare the service class:
service: class: net.officefloor.tutorial.springrestcors.GlobalCorsService
public class GlobalCorsService {
public void service(ObjectResponse<String> response) {
response.send("Hello from global CORS endpoint");
}
}
CorsConfigurationSource beanA CorsConfigurationSource bean gives full programmatic control over CORS rules and is consumed by Spring Security's CorsFilter when Spring Security is on the classpath. For OfficeFloor-managed endpoints, mirror the same rules via addCorsMappings() so that Spring MVC's CORS machinery also picks them up:
@Configuration
public class CorsConfigurationSourceConfig implements WebMvcConfigurer {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://example.com");
config.addAllowedMethod("GET");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/cors/cors-config-source/**", config);
return source;
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/cors/cors-config-source/**")
.allowedOrigins("https://example.com");
}
}
Again, the endpoint YAML files only declare the service class:
service: class: net.officefloor.tutorial.springrestcors.GlobalCorsService
Tests send an Origin header. When the origin is on the allow-list the response carries Access-Control-Allow-Origin. When it is not, Spring returns 403 Forbidden and the header is absent:
@SpringBootTest
@AutoConfigureMockMvc
public class SpringRestCorsTest {
@Autowired
private MockMvc mvc;
@Test
public void compositionCors_allowedOrigin() throws Exception {
mvc.perform(get("/cors/composition")
.header("Origin", "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().string("Access-Control-Allow-Origin", "https://example.com"))
.andExpect(content().string(equalTo("Hello from composition CORS endpoint")));
}
@Test
public void compositionCors_rejectedOrigin() throws Exception {
mvc.perform(get("/cors/composition")
.header("Origin", "https://evil.com"))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
@Test
public void annotationCors_allowedOrigin() throws Exception {
mvc.perform(get("/cors/annotation")
.header("Origin", "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().string("Access-Control-Allow-Origin", "https://example.com"))
.andExpect(content().string(equalTo("Hello from annotation CORS endpoint")));
}
@Test
public void annotationCors_rejectedOrigin() throws Exception {
mvc.perform(get("/cors/annotation")
.header("Origin", "https://evil.com"))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
@Test
public void allMethodsCors_allowedOrigin() throws Exception {
mvc.perform(get("/cors/all-methods")
.header("Origin", "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().string("Access-Control-Allow-Origin", "https://example.com"))
.andExpect(content().string(equalTo("Hello from all-methods CORS endpoint")));
}
@Test
public void allMethodsCors_rejectedOrigin() throws Exception {
mvc.perform(get("/cors/all-methods")
.header("Origin", "https://evil.com"))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
@Test
public void mvcConfigurer_allowedOrigin() throws Exception {
mvc.perform(get("/cors/mvc-configurer/origin")
.header("Origin", "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().string("Access-Control-Allow-Origin", "https://example.com"))
.andExpect(content().string(equalTo("Hello from global CORS endpoint")));
}
@Test
public void mvcConfigurer_rejectedOrigin() throws Exception {
mvc.perform(get("/cors/mvc-configurer/origin")
.header("Origin", "https://evil.com"))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
@Test
public void corsConfigSource_allowedOrigin() throws Exception {
mvc.perform(get("/cors/cors-config-source/origin")
.header("Origin", "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().string("Access-Control-Allow-Origin", "https://example.com"))
.andExpect(content().string(equalTo("Hello from global CORS endpoint")));
}
@Test
public void corsConfigSource_rejectedOrigin() throws Exception {
mvc.perform(get("/cors/cors-config-source/origin")
.header("Origin", "https://evil.com"))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
}
The Spring REST OpenAPI tutorial demonstrates automatic OpenAPI specification generation for OfficeFloor REST endpoints.