Spring Boot
Security
CORS
Web Development
Java

Spring Boot Security CORS

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

CORS (Cross-Origin Resource Sharing) errors in Spring Boot happen when the browser blocks requests from a frontend on one domain to a backend on another. Spring Security adds an extra layer — it processes requests before your CORS configuration takes effect, so CORS must be configured within the security filter chain. The fix is to call cors() on the HttpSecurity object and define a CorsConfigurationSource bean.

The Error

When CORS is misconfigured, the browser blocks the request with:

 
Access to XMLHttpRequest at 'http://localhost:8080/api/users'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

This happens because the browser sends a preflight OPTIONS request, and Spring Security rejects it before your controller or CORS filter runs.

Fix: Spring Boot 3.x / Spring Security 6.x

java
1@Configuration
2@EnableWebSecurity
3public class SecurityConfig {
4
5    @Bean
6    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
7        http
8            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
9            .csrf(csrf -> csrf.disable())
10            .authorizeHttpRequests(auth -> auth
11                .requestMatchers("/api/public/**").permitAll()
12                .anyRequest().authenticated()
13            );
14        return http.build();
15    }
16
17    @Bean
18    public CorsConfigurationSource corsConfigurationSource() {
19        CorsConfiguration config = new CorsConfiguration();
20        config.setAllowedOrigins(List.of("http://localhost:3000", "https://myapp.com"));
21        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
22        config.setAllowedHeaders(List.of("*"));
23        config.setAllowCredentials(true);
24        config.setMaxAge(3600L);
25
26        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
27        source.registerCorsConfiguration("/**", config);
28        return source;
29    }
30}

The key is cors(cors -> cors.configurationSource(...)) — this tells Spring Security to apply CORS before authentication checks, allowing preflight OPTIONS requests through.

Spring Boot 2.x / Spring Security 5.x

java
1@Configuration
2@EnableWebSecurity
3public class SecurityConfig extends WebSecurityConfigurerAdapter {
4
5    @Override
6    protected void configure(HttpSecurity http) throws Exception {
7        http
8            .cors()  // Enable CORS with default config source
9            .and()
10            .csrf().disable()
11            .authorizeRequests()
12                .antMatchers("/api/public/**").permitAll()
13                .anyRequest().authenticated();
14    }
15
16    @Bean
17    public CorsConfigurationSource corsConfigurationSource() {
18        CorsConfiguration config = new CorsConfiguration();
19        config.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
20        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
21        config.setAllowedHeaders(Arrays.asList("*"));
22        config.setAllowCredentials(true);
23
24        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
25        source.registerCorsConfiguration("/**", config);
26        return source;
27    }
28}

Alternative: @CrossOrigin on Controllers

For simple cases, annotate controllers directly:

java
1@RestController
2@RequestMapping("/api/users")
3@CrossOrigin(origins = "http://localhost:3000")
4public class UserController {
5
6    @GetMapping
7    public List<User> getUsers() {
8        return userService.findAll();
9    }
10
11    // Per-method override
12    @CrossOrigin(origins = "https://admin.myapp.com")
13    @DeleteMapping("/{id}")
14    public void deleteUser(@PathVariable Long id) {
15        userService.delete(id);
16    }
17}

@CrossOrigin works for simple apps but does not integrate with Spring Security's filter chain. For secured endpoints, use the SecurityFilterChain approach.

Global CORS via WebMvcConfigurer

java
1@Configuration
2public class WebConfig implements WebMvcConfigurer {
3
4    @Override
5    public void addCorsMappings(CorsRegistry registry) {
6        registry.addMapping("/api/**")
7            .allowedOrigins("http://localhost:3000")
8            .allowedMethods("GET", "POST", "PUT", "DELETE")
9            .allowedHeaders("*")
10            .allowCredentials(true)
11            .maxAge(3600);
12    }
13}

This configures CORS at the MVC level. However, when Spring Security is present, you must also enable CORS in the security config — otherwise, Security's filter chain rejects the preflight before MVC sees it.

CORS Configuration Options

java
1CorsConfiguration config = new CorsConfiguration();
2
3// Which origins can access (use specific origins, not *)
4config.setAllowedOrigins(List.of("http://localhost:3000"));
5
6// Or use patterns for subdomain matching
7config.setAllowedOriginPatterns(List.of("https://*.myapp.com"));
8
9// Which HTTP methods are allowed
10config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
11
12// Which request headers the client can send
13config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Requested-With"));
14
15// Which response headers the client can read
16config.setExposedHeaders(List.of("X-Total-Count", "X-Page-Number"));
17
18// Whether cookies/auth headers are included
19config.setAllowCredentials(true);
20
21// How long the browser caches preflight results (seconds)
22config.setMaxAge(3600L);

Development: Allow All Origins

java
1@Bean
2@Profile("dev")
3public CorsConfigurationSource corsConfigurationSource() {
4    CorsConfiguration config = new CorsConfiguration();
5    config.setAllowedOriginPatterns(List.of("*"));
6    config.setAllowedMethods(List.of("*"));
7    config.setAllowedHeaders(List.of("*"));
8    config.setAllowCredentials(true);
9
10    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
11    source.registerCorsConfiguration("/**", config);
12    return source;
13}

Use @Profile("dev") to restrict permissive CORS to development only. Never allow * origins with credentials in production.

Common Pitfalls

  • CORS not in Security config: Defining CORS only in WebMvcConfigurer without enabling it in HttpSecurity means Spring Security blocks preflight OPTIONS requests before MVC processes them. Always add .cors() to your security config.
  • allowedOrigins("*") with allowCredentials(true): This combination is not allowed by the CORS spec. Use allowedOriginPatterns("*") instead, or list specific origins.
  • Forgetting OPTIONS in allowed methods: Preflight requests use the OPTIONS method. If your allowed methods list does not include it, preflights fail. Most configurations should include OPTIONS.
  • CSRF blocking POST/PUT/DELETE: Even with CORS configured, Spring Security's CSRF protection rejects non-GET requests without a token. Disable CSRF for stateless APIs or include the CSRF token in requests.
  • Multiple CORS configurations conflicting: Having both @CrossOrigin, WebMvcConfigurer, and SecurityFilterChain CORS configs can cause unexpected behavior. Use one approach consistently.

Summary

  • Configure CORS inside SecurityFilterChain with .cors(cors -> cors.configurationSource(...)) for Spring Security integration
  • Define a CorsConfigurationSource bean with specific allowed origins, methods, and headers
  • @CrossOrigin works for simple cases without Spring Security
  • WebMvcConfigurer.addCorsMappings() configures MVC-level CORS but needs Security-level CORS too when Spring Security is present
  • Never use allowedOrigins("*") with allowCredentials(true) — use allowedOriginPatterns instead
  • Use @Profile("dev") for permissive development CORS configurations

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.