CORS Configuration
IntermediateCross-Origin Resource Sharing is configured globally via WebMvcConfigurer or per-controller with @CrossOrigin to control which origins browsers allow.
Overview
Browsers enforce the Same-Origin Policy: JavaScript on origin A cannot call APIs on origin B unless the server explicitly grants permission through CORS headers. When your Spring Boot API is consumed by a frontend hosted on a different domain (e.g., app.example.com → api.example.com), the browser sends a preflight OPTIONS request asking whether the actual request is permitted. Spring MVC intercepts this and adds the appropriate Access-Control-Allow-* response headers if the origin is configured. Misconfiguring CORS — especially using wildcard "*" with credentials — is a security vulnerability. In Spring Boot 3 / Spring Security 6 CORS must be configured in the security filter chain; a CorsFilter bean alone is insufficient when Spring Security is present because the security filter runs before the MVC CORS filter.
Global CORS via WebMvcConfigurer
The cleanest approach for most APIs: define allowed origins, methods, and headers globally so every controller inherits them without per-class annotations.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(
"https://app.example.com",
"https://staging.example.com"
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("X-Correlation-Id", "Location")
.allowCredentials(true) // allows cookies/Authorization headers
.maxAge(3600); // cache preflight for 1 hour
// Public endpoints — allow all origins, no credentials
registry.addMapping("/public/**")
.allowedOrigins("*")
.allowedMethods("GET")
.allowCredentials(false);
}
}CORS with Spring Security 6
When Spring Security is active you must configure CORS in the SecurityFilterChain — the MVC-level config is bypassed by the security filter. Provide a CorsConfigurationSource bean.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
}@CrossOrigin for per-controller overrides
@CrossOrigin can override the global config per controller or per method — useful for public endpoints that need wider access than the global policy.
// Allow a specific third-party origin on this controller only
@RestController
@RequestMapping("/widgets")
@CrossOrigin(
origins = "https://partner-site.com",
methods = {RequestMethod.GET},
maxAge = 1800
)
public class WidgetController {
// This endpoint allows ALL origins (public read-only)
@GetMapping("/{id}")
@CrossOrigin("*")
public Widget getWidget(@PathVariable Long id) { ... }
// This endpoint uses the class-level @CrossOrigin
@PostMapping
public Widget createWidget(@RequestBody WidgetDto dto) { ... }
}
// SECURITY NOTE: never combine allowedOrigins("*") with allowCredentials(true)
// Browsers reject it; it is also a security risk (any site can send credentialed requests).Key Points to Remember
- 1CORS is enforced by browsers, not servers — server-to-server calls are never blocked by CORS.
- 2Spring Security must be involved in CORS config; WebMvcConfigurer alone is bypassed when security is active.
- 3Never use allowedOrigins("*") with allowCredentials(true) — browsers reject it and it is a security vulnerability.
- 4Preflight OPTIONS requests must return 200 quickly; Spring handles them automatically when CORS is configured.
- 5Use allowedOrigins with explicit domains in production; use environment-specific properties to avoid hardcoding.
- 6exposedHeaders lists headers the browser JavaScript can read from the response — only listed headers are accessible.
Interview Questions
Sign in to ask AriaWhat is the difference between a simple CORS request and a preflight request?
Why does configuring CORS in WebMvcConfigurer not work when Spring Security is present?
Can a backend API be exploited through CORS misconfiguration? Give an example.
What HTTP headers does the server return to permit a cross-origin request?
How would you allow multiple origins dynamically (e.g. from a database) rather than hardcoding them?
Ask Aria about CORS Configuration
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.