Add the cors-csrf module
This commit is contained in:
82
cors-csrf/docs/01-two-layers.md
Normal file
82
cors-csrf/docs/01-two-layers.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 1. Two layers, one word
|
||||
|
||||
*Next: [2. Who resolves the CorsConfigurationSource](02-who-resolves-the-source.md)*
|
||||
|
||||
A Spring Boot application can be told about CORS in two entirely separate places, and the two
|
||||
places do not talk to each other unless you make them.
|
||||
|
||||
**The MVC layer.** `WebMvcConfigurer.addCorsMappings(..)` and `@CrossOrigin` register a
|
||||
`CorsConfiguration` with Spring MVC's handler mappings. It is consulted inside
|
||||
`DispatcherServlet`, when the request is being matched to a handler method.
|
||||
|
||||
**The security layer.** `HttpSecurity.cors(..)` puts a `org.springframework.web.filter.CorsFilter`
|
||||
into the security filter chain. It runs at order **1000** — between `HeaderWriterFilter`
|
||||
(900) and `CsrfFilter` (1100), and a long way above `AuthorizationFilter` (4200).
|
||||
|
||||
The security filter chain runs to completion before `DispatcherServlet` is ever entered. So if
|
||||
the security chain rejects a request, MVC's CORS configuration is not merely ignored: the code
|
||||
that reads it never executes.
|
||||
|
||||
## Why that specifically breaks preflights
|
||||
|
||||
A CORS preflight is not a special protocol. It is an ordinary `OPTIONS` request carrying two
|
||||
headers:
|
||||
|
||||
```
|
||||
OPTIONS /api/data HTTP/1.1
|
||||
Origin: https://spa.example.com
|
||||
Access-Control-Request-Method: POST
|
||||
```
|
||||
|
||||
The browser sends it **without credentials** — no cookies, no `Authorization` header, by
|
||||
design. Against `anyRequest().authenticated()` that request is anonymous, and anonymous requests
|
||||
are denied. The rejection happens at order 4200, three thousand two hundred slots before
|
||||
`DispatcherServlet` and about four thousand before your `addCorsMappings` call matters.
|
||||
|
||||
[`docs/output/01-mvc-only.txt`](output/01-mvc-only.txt) is that state: an eleven-filter chain
|
||||
with no `CorsFilter` in it, and a preflight answered `401`.
|
||||
|
||||
## The fix is one line, and it is not on the MVC layer
|
||||
|
||||
```java
|
||||
http.cors(Customizer.withDefaults())
|
||||
```
|
||||
|
||||
That is [`MvcBridgeSecurityConfig`](../src/main/java/com/ankurm/cors/config/MvcBridgeSecurityConfig.java),
|
||||
and [`docs/output/02-mvc-bridge.txt`](output/02-mvc-bridge.txt) is the identical application
|
||||
answering `200`. The MVC configuration was fine all along. Nothing was reading it.
|
||||
|
||||
`CorsFilter` short-circuits every preflight it sees:
|
||||
|
||||
```java
|
||||
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
|
||||
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
|
||||
return; // the chain stops here
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
```
|
||||
|
||||
Read that `if` carefully, because chapter 2 turns on it: the filter returns on **every**
|
||||
preflight, whether or not it found a configuration to apply.
|
||||
|
||||
## One thing that changes when you move the configuration
|
||||
|
||||
Moving CORS from `addCorsMappings` to a `CorsConfigurationSource` bean is not a pure
|
||||
relocation. Compare the two transcripts:
|
||||
|
||||
```
|
||||
02-mvc-bridge.txt Access-Control-Max-Age: 1800
|
||||
03-security-source.txt (nothing)
|
||||
```
|
||||
|
||||
`CorsRegistration` — the builder behind `addCorsMappings` — defaults `maxAge` to
|
||||
1800 seconds. A bare `CorsConfiguration` leaves it `null`, and a preflight response with no
|
||||
`Access-Control-Max-Age` is not cached, so the browser preflights **every single cross-origin
|
||||
call**. Two round trips instead of one, forever, with nothing in any log to suggest it.
|
||||
|
||||
```java
|
||||
configuration.setMaxAge(1800L);
|
||||
```
|
||||
|
||||
---
|
||||
*Next: [2. Who resolves the CorsConfigurationSource](02-who-resolves-the-source.md)*
|
||||
Reference in New Issue
Block a user