80 lines
3.3 KiB
Markdown
80 lines
3.3 KiB
Markdown
# 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)*
|