Home/Learn/Spring Boot/CORS Configuration

CORS Configuration

Intermediate
Web / REST

Cross-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.

Java — global CORS via WebMvcConfigurer
@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.

Java — CORS inside Spring Security 6 filter chain
@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.

Java — per-controller @CrossOrigin
// 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 Aria
1

What is the difference between a simple CORS request and a preflight request?

EasyAmazon
2

Why does configuring CORS in WebMvcConfigurer not work when Spring Security is present?

MediumNetflix
3

Can a backend API be exploited through CORS misconfiguration? Give an example.

HardGoogle
4

What HTTP headers does the server return to permit a cross-origin request?

EasyMeta
5

How would you allow multiple origins dynamically (e.g. from a database) rather than hardcoding them?

MediumShopify

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.

Loading discussion…