skip to content

CORS Integration

The cors() DSL wires a CorsConfigurationSource into the filter chain early enough that preflight OPTIONS requests are not rejected by authorization. That ordering is the whole reason CORS breaks under Spring Security.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is CORS, and how do you enable it in a Spring Security application?

level: juniorimportance: must knowfreq 70%

answer

  1. http.cors(Customizer.withDefaults())
  2. CorsConfigurationSource bean named corsConfigurationSource
  3. browser-only, opt-in via Access-Control-Allow-*
  4. filter chain runs before MVC
  5. UrlBasedCorsConfigurationSource per path

basics

~20 s

CORS lets a browser page from one origin call an API on another origin. In Spring Security you turn it on with http.cors() in the SecurityFilterChain and provide a CorsConfigurationSource bean that lists allowed origins, methods, and headers.

solid answer

~30 s

CORS (Cross-Origin Resource Sharing) is a browser rule: JavaScript on origin A (scheme+host+port) may only read a response from origin B if B sends Access-Control-Allow-* headers permitting it. In Spring Security you enable it inside the SecurityFilterChain with http.cors(Customizer.withDefaults()). That activates Spring Security's CorsFilter, which reads a CorsConfigurationSource bean (usually named corsConfigurationSource). You typically register a UrlBasedCorsConfigurationSource with a CorsConfiguration specifying setAllowedOrigins, setAllowedMethods, setAllowedHeaders, and optionally setAllowCredentials. Without http.cors(), Spring Security ignores your CORS config for anything the filter chain protects, and the preflight OPTIONS request can be blocked by authorization before your controller ever runs.

code

java · 23 lines
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors(Customizer.withDefaults())   // wires in the CorsFilter using the bean below
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
        return http.build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://app.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

go deeper

for a junior

Must know CORS is a browser cross-origin rule and that http.cors() plus a CorsConfigurationSource bean enables it.

for a middle

Should explain why the security chain (not @CrossOrigin) is the right place and what the source bean contains.

for a senior

Should distinguish CORS from authz and know the bean-name resolution and per-path source mapping.

for a principal

Frames CORS as one browser-side control among many, and reasons about same-origin deployment as the way to avoid it entirely.

## What CORS is An **origin** is the triple scheme + host + port (e.g. `https://app.example.com:443`). Browsers enforce the **Same-Origin Policy**: JavaScript loaded from origin A cannot, by default, read responses from origin B. **CORS (Cross-Origin Resource Sharing)** is the standard by which server B *opts in* to being called cross-origin by sending `Access-Control-Allow-*` response headers. CORS is enforced entirely by the **browser** — a non-browser client (curl, another server) ignores it. So CORS is not authentication or authorization; it only controls which web pages may *read* your responses in a browser. ## Enabling it in Spring Security Spring Security has its own CORS integration because the **security filter chain runs before Spring MVC**. You enable it in your `SecurityFilterChain`: ```java http.cors(Customizer.withDefaults()); ``` `Customizer.withDefaults()` tells the `CorsConfigurer` to look up a bean of type `CorsConfigurationSource` (by default the bean named `corsConfigurationSource`) and install a `CorsFilter` into the chain that uses it. ## The CorsConfigurationSource bean ```java @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://app.example.com")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); config.setAllowedHeaders(List.of("Authorization", "Content-Type")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } ``` `CorsConfiguration` is the object that becomes the `Access-Control-Allow-*` headers. `UrlBasedCorsConfigurationSource` maps URL patterns to configurations, so you can have different rules per path. ## Why not just @CrossOrigin or a WebMvcConfigurer? `@CrossOrigin` on a controller and `WebMvcConfigurer.addCorsMappings` configure CORS at the **MVC layer**, which sits *after* the security filter chain. If Spring Security rejects the request first (for example a preflight `OPTIONS` that has no credentials), your controller-level CORS config never runs. That is why, when Spring Security is on the classpath, you must configure CORS through `http.cors()` so the `CorsFilter` sits inside the security chain. ## Common gotchas - Forgetting `http.cors()` — the bean exists but is never wired into the security chain. - Wrong bean name/type — `Customizer.withDefaults()` resolves `CorsConfigurationSource`; a mismatched name is silently ignored. - Setting allowed origins to `*` together with credentials (illegal — covered in a separate question). ## When to use Any time a browser SPA served from one origin (e.g. `localhost:3000` or a CDN domain) calls your Spring API on a different origin. If the UI and API share an origin (same host/port, e.g. served behind one reverse proxy), you do not need CORS at all.

  • Does CORS protect your server from malicious clients?
    No. CORS is enforced by the browser and only governs whether page JavaScript may read a response. A non-browser client (curl, another backend) ignores CORS entirely, so it is not a substitute for authentication/authorization.
  • What happens if you define the CorsConfigurationSource bean but forget http.cors()?
    The security filter chain never installs the CorsFilter, so no Access-Control-Allow-* headers are added and cross-origin browser calls fail. The bean is effectively dead configuration.

saying these in an interview costs you the question

  • Claiming CORS blocks malicious/non-browser clients or replaces authentication
  • Saying @CrossOrigin alone is enough when Spring Security is present
  • Thinking CORS is a server-to-server restriction rather than a browser policy

context

open as a page

Why must Spring Security's CorsFilter be ordered before the authorization filter, and how does that affect preflight OPTIONS requests?

level: middleimportance: must knowfreq 65%

basics

~20 s

Browsers send a preflight OPTIONS request with no credentials before the real cross-origin call. Spring Security's CorsFilter runs early, before authorization, so it can answer the preflight and permit it instead of the authorization filter rejecting it as unauthenticated.

open as a page

How do allowedOrigins, allowedOriginPatterns, and allowCredentials interact, and what is the wildcard-plus-credentials pitfall?

level: seniorimportance: must knowfreq 60%

basics

~20 s

If allowCredentials is true you cannot use "*" for origins, because the browser refuses Access-Control-Allow-Origin: * together with credentials. Either list exact origins with setAllowedOrigins, or use setAllowedOriginPatterns to match wildcards while still reflecting a concrete origin.

open as a page

In a Spring Security app, why configure CORS via the CorsConfigurationSource in the filter chain rather than WebMvcConfigurer.addCorsMappings or @CrossOrigin?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The security filter chain runs before Spring MVC. If CORS lives only in WebMvcConfigurer or @CrossOrigin, the security filters can reject a preflight before MVC handles it. Configuring a CorsConfigurationSource plus http.cors() puts CORS inside the filter chain where it runs first.

open as a page

Designing CORS for a cookie-based (refresh-token) JWT SPA on a separate origin: what must be configured and hardened, and what are the caching and header pitfalls?

level: principalimportance: should knowfreq 35%

basics

~20 s

Set an exact origin allowlist (or a tight pattern), setAllowCredentials(true) so cookies flow, and expose only needed response headers via setExposedHeaders. Keep the origin list minimal, pair with SameSite cookies and CSRF defenses, and rely on Vary: Origin for correct caching.

open as a page