1
0

Add the cors-csrf module

This commit is contained in:
2026-08-28 09:33:22 +05:30
parent 73ab67b171
commit cad813e1ae
49 changed files with 3338 additions and 13 deletions

View File

@@ -0,0 +1,79 @@
# 3. The three identical 403s
*Prev: [2. Who resolves the CorsConfigurationSource](02-who-resolves-the-source.md) · Next: [4. Preflight handlers](04-preflight-handlers.md)*
`DefaultCorsProcessor` runs three checks on a preflight, in order: origin, method, request
headers. All three failures produce the same thing.
```
HTTP/1.1 403
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Invalid CORS request
```
Same status, same body, byte for byte, no `Access-Control-*` header to distinguish them. The
assertion in `CorsContractTests.threeRejectionsLookIdentical` compares the three bodies for
equality, so if a future version starts distinguishing them, that test fails.
The only place the difference exists is a DEBUG log line, and the three are worth memorising
because they are the fastest CORS diagnosis available:
```
o.s.web.cors.DefaultCorsProcessor : Reject: 'https://evil.example.com' origin is not allowed
o.s.web.cors.DefaultCorsProcessor : Reject: HTTP 'DELETE' is not allowed
o.s.web.cors.DefaultCorsProcessor : Reject: headers '[authorization]' are not allowed
```
Turn them on with:
```yaml
logging.level.org.springframework.web.cors: DEBUG
```
The complete set of messages, read out of the class's constant pool, is five:
| Message | Meaning |
|---|---|
| `Skip: no CORS configuration has been provided` | The source returned `null` for this path — chapter 2 |
| `Skip: response already contains "Access-Control-Allow-Origin"` | Something upstream already handled it |
| `Reject: origin is malformed` | The `Origin` header did not parse |
| `Reject: '…' origin is not allowed` | |
| `Reject: HTTP '…' is not allowed` | |
| `Reject: headers '[…]' are not allowed` | |
## Status codes, and what each one means
Collecting the states this module reproduces:
| What you see | What it means |
|---|---|
| `401`/`403`, no `Access-Control-*` at all | No `CorsFilter` in the chain. The preflight was judged by authorization — chapter 1 |
| `200`, no `Access-Control-*` | `CorsFilter` is present and found no configuration for this path — chapter 2 |
| `403`, `Invalid CORS request` | `CorsFilter` is present and rejected origin, method or headers — this chapter |
| `200` with `Access-Control-Allow-Origin` | It worked |
| `404`, no `Access-Control-*` | The path is outside the pattern you registered. Common with `/api/**` versus a mis-typed URL |
The browser reports the same "blocked by CORS policy" for the first four rows. Two of them are
not CORS problems.
## What a **simple** request does
Only preflighted requests get intercepted. A simple `GET` runs the whole chain, so an
unauthenticated one returns 401 — **carrying** the CORS header, because `CorsFilter` at
1000 already wrote it before `AuthorizationFilter` at 4200 rejected the request:
```
HTTP/1.1 401
Access-Control-Allow-Origin: https://spa.example.com
Access-Control-Allow-Credentials: true
```
That is the good case: the SPA's `fetch` resolves and the code can read `response.status`. It is
also the reason "my POST fails but my GET returns a readable 401" is a coherent bug report and
not a contradiction.
---
*Prev: [2. Who resolves the CorsConfigurationSource](02-who-resolves-the-source.md) · Next: [4. Preflight handlers](04-preflight-handlers.md)*