Spring Boot CORS and CSRF
Browsers enforce strict safety rules, and two of them confuse many new developers. CORS controls which websites may call an API from a browser. CSRF protects logged-in users from forged requests sent by hostile pages. Both names look alike, yet they solve different problems.
Spring Boot and Spring Security handle each rule through short configuration blocks.
Understanding CORS
CORS stands for Cross-Origin Resource Sharing. An origin combines three parts: protocol, host, and port. Two addresses with any difference in those parts count as different origins.
| Address A | Address B | Same Origin? |
|---|---|---|
| http://localhost:8080 | http://localhost:8080/api | Yes |
| http://localhost:3000 | http://localhost:8080 | No (port differs) |
| https://shop.com | http://shop.com | No (protocol differs) |
| https://shop.com | https://api.shop.com | No (host differs) |
The Gated Community Idea
A gated community guard checks every visitor against a guest list. Residents walk in freely, while outsiders need an approved name on the list. The browser plays the guard. A page from one origin may call another origin only when the target server approves that origin in its reply headers.
React app Spring Boot API
localhost:3000 localhost:8080
| |
| GET /api/books ---------> |
| Origin: localhost:3000 |
| |
| <--------- Access-Control-Allow-Origin: localhost:3000
| |
Browser allows the response (guest list approved)
Without the approval header, the browser blocks the response and prints a CORS error in the console. The server itself ran the request; the browser simply refuses to hand the answer to the page.
Allowing One Controller
@RestController
@RequestMapping("/api/books")
@CrossOrigin(origins = "http://localhost:3000")
public class BookController {
// endpoints
}Allowing the Whole Application
A global configuration keeps the guest list in one place. When Spring Security is present, register the settings inside the security chain.
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000", "https://shop.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(Customizer.withDefaults());
// other rules
return http.build();
}MaxAge tells the browser to cache the approval for one hour, which reduces repeated preflight checks.
Preflight Requests
Before a risky request, such as DELETE or one with an Authorization header, the browser sends a small OPTIONS request that asks permission. The server answers with the allowed methods and headers. Spring handles this exchange automatically once CORS is configured.
Understanding CSRF
CSRF stands for Cross-Site Request Forgery. Browsers attach cookies to every request sent to a site, even when a different page triggers the request. An attacker page can exploit that habit and send an action to a site where the victim already holds a login cookie.
The Rubber Stamp Idea
Imagine a company where any letter carrying the official stamp counts as approved. A stranger tricks an employee into stamping a blank letter, then fills in a money transfer. The stamp proves nothing about who wrote the content. A forged request rides on the login cookie in the same way.
1. User logs in to bank.com (cookie stored) 2. User opens evil-page.com 3. Evil page sends POST bank.com/transfer 4. Browser attaches the bank cookie 5. Bank sees a valid cookie --> action runs (without CSRF protection)
How the CSRF Token Defends
The server creates a secret random token for each session and expects that token inside every state-changing request. A hostile page cannot read the token because of the same-origin rule, so its forged request fails with a 403 error.
Normal form: token included --> request accepted Forged request: no token --> 403 Forbidden
Spring Security turns CSRF protection on by default for session-based applications. Server-rendered forms receive the token automatically through Thymeleaf.
When to Disable CSRF
The risk exists because browsers attach cookies on their own. A stateless API that reads a JWT from the Authorization header carries no such cookie, so forged requests lack the header. Such APIs commonly disable CSRF.
http.csrf(csrf -> csrf.disable());Keep CSRF protection active for any application that relies on session cookies.
Side-by-Side Summary
| Point | CORS | CSRF |
|---|---|---|
| Goal | Control which sites may read API responses | Stop forged actions from hostile pages |
| Enforced by | Browser, using server headers | Server, using a secret token |
| Typical fix | Allow trusted origins | Require a CSRF token or use header-based tokens |
Best Practices
- List exact origins instead of the wildcard star, especially when cookies or credentials are involved.
- Allow only the methods and headers that the client truly needs.
- Test CORS from a real browser, because command-line tools ignore it.
- Disable CSRF only for stateless, token-based APIs.
- Use HTTPS in production so that cookies and tokens stay private.
Correct CORS and CSRF settings let trusted frontends work smoothly and block everything else. Understanding the difference saves hours of confusing browser errors.
