Explain the WebFilter-based architecture of the WebFlux security chain and how it differs from the servlet FilterChainProxy model.
answer
- WebFilterChainProxy -> ordered SecurityWebFilterChains
- SecurityWebFilterChain = matcher + WebFilters
- no Servlet API, WebFilter returns Mono<Void>
- state in Reactor Context (ReactiveSecurityContextHolder)
- multiple chains by @Order, first match wins
basics
~20 sIn WebFlux, security is a chain of Spring WebFilters (not servlet Filters). A WebFilterChainProxy delegates to the matching SecurityWebFilterChain, whose ordered WebFilters do authentication, context loading, and authorization — all returning Mono so nothing blocks the event loop.
solid answer
~40 sWebFlux has no Servlet API, so Spring Security replaces the servlet FilterChainProxy/DelegatingFilterProxy model with a reactive one built on WebFilter. A single WebFilterChainProxy is registered in the WebFlux handler pipeline; it holds an ordered list of SecurityWebFilterChain instances, each pairing a ServerWebExchangeMatcher with a list of WebFilters. For a request it picks the first matching chain and runs its filters — SecurityContextServerWebExchangeWebFilter (loads context), AuthenticationWebFilter(s), authorization filter (AuthorizationWebFilter driven by ReactiveAuthorizationManager), CSRF, CORS, exception-translation — all returning Mono<Void> and chaining via WebFilterChain.filter(exchange). Security state lives in the Reactor Context via ReactiveSecurityContextHolder rather than a ThreadLocal, because a request may hop threads. Multiple SecurityWebFilterChain beans let you apply different rules to different path groups, ordered by @Order. The whole pipeline is non-blocking end to end.
code
java · 22 lines// Two chains: JWT-protected API, form-login web app. First matching chain wins.
@Bean
@Order(1)
SecurityWebFilterChain apiChain(ServerHttpSecurity http) {
return http
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**"))
.authorizeExchange(e -> e.anyExchange().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.build();
}
@Bean
@Order(2)
SecurityWebFilterChain webChain(ServerHttpSecurity http) {
return http
.authorizeExchange(e -> e
.pathMatchers("/login", "/css/**").permitAll()
.anyExchange().authenticated())
.formLogin(Customizer.withDefaults())
.build();
}go deeper
Know security is a chain of filters that runs before your handler.
Distinguish WebFilter from servlet Filter and name WebFilterChainProxy/SecurityWebFilterChain.
Explain the ordered filters, Reactor-Context state, and multiple-chain matching.
Reason about chain ordering as a correctness/security boundary, context propagation across operators, custom filter placement, and event-loop safety in a fully reactive system.
## Two different runtimes **Servlet (Spring MVC):** Security is a servlet `Filter`. Boot registers a `DelegatingFilterProxy` (`springSecurityFilterChain`) that delegates to **`FilterChainProxy`**, which holds an ordered list of **`SecurityFilterChain`** objects, each a list of servlet `Filter`s (e.g. `UsernamePasswordAuthenticationFilter`, `BasicAuthenticationFilter`, `FilterSecurityInterceptor`/`AuthorizationFilter`). State lives in a **`ThreadLocal`** via `SecurityContextHolder` because the servlet model is thread-per-request. **Reactive (WebFlux):** There is **no Servlet API**. Requests are handled by a `WebHandler` pipeline of Spring **`WebFilter`**s. Spring Security provides **`WebFilterChainProxy`**, registered as a `WebFilter`, that holds an ordered list of **`SecurityWebFilterChain`**s. Each `SecurityWebFilterChain` pairs a **`ServerWebExchangeMatcher`** (which exchanges it applies to) with an ordered list of `WebFilter`s. ## The WebFilter contract ```java public interface WebFilter { Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain); } ``` Each filter does its work reactively and calls `chain.filter(exchange)` to continue, composing everything into one `Mono<Void>`. Nothing blocks the Netty event-loop thread. ## Ordered security filters inside a chain When a request matches a `SecurityWebFilterChain`, its filters run in a fixed order (see the `SecurityWebFiltersOrder` enum), roughly: 1. `ServerWebExchangeReactorContextWebFilter` / context setup. 2. **`SecurityContextServerWebExchangeWebFilter`** — loads any existing `SecurityContext` into the Reactor Context. 3. CORS, CSRF (`CsrfWebFilter`), header-writing filters. 4. **`AuthenticationWebFilter`(s)** — convert credentials and call the `ReactiveAuthenticationManager`. 5. `ReactorContextWebFilter` propagation, `LogoutWebFilter`, `ExceptionTranslationWebFilter` (maps `AuthenticationException`/`AccessDeniedException` to 401/403). 6. **`AuthorizationWebFilter`** — consults `ReactiveAuthorizationManager` (from `authorizeExchange`) to permit/deny. ## Where security state lives Because a reactive request can execute on **different threads** as it moves through operators, a `ThreadLocal` is useless. Spring uses **`ReactiveSecurityContextHolder`**, which stores the `SecurityContext` in the **Reactor `Context`** that flows with the subscription. `@AuthenticationPrincipal`, method security, and `authorizeExchange` all read from it. ## Multiple chains You can register **several `SecurityWebFilterChain` beans**, each with a `securityMatcher(...)` and its own rules — e.g. one for `/api/**` using JWT resource-server, another for `/**` using form login. They are consulted in **bean `@Order`**; the **first matching chain handles the request** (others are skipped), so ordering is a correctness concern. ```java @Bean @Order(1) SecurityWebFilterChain api(ServerHttpSecurity http) { return http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**")) .authorizeExchange(e -> e.anyExchange().authenticated()) .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())) .build(); } @Bean @Order(2) SecurityWebFilterChain web(ServerHttpSecurity http) { return http.authorizeExchange(e -> e.anyExchange().authenticated()) .formLogin(Customizer.withDefaults()) .build(); } ``` ## Method security The reactive equivalent of `@EnableMethodSecurity` is **`@EnableReactiveMethodSecurity`**, enabling `@PreAuthorize`/`@PostAuthorize` on methods returning `Mono`/`Flux`; enforcement reads the reactive security context. ## Gotchas / principal-level concerns - **Never block** anywhere in a security `WebFilter` — a single blocking call can stall the shared event loop. - **Chain ordering** decides which ruleset applies; a broad matcher on an earlier `@Order` shadows later chains (an authz hole). - Don't mix servlet security types (`SecurityFilterChain`, `HttpSecurity`, `SecurityContextHolder`) into a reactive app — they won't take effect and mislead readers. - Context propagation across custom `publishOn`/`subscribeOn` or external reactive libraries must preserve the Reactor Context, or the principal 'disappears'. - `WebFilter` order vs `SecurityWebFiltersOrder`: custom auth filters must be inserted with `addFilterAt/Before/After` using that enum, not arbitrary positions. ## When it matters Understanding this model is essential when composing multiple auth mechanisms, debugging 'lost principal' issues, building custom `WebFilter`s, or reasoning about performance and back-pressure in a fully reactive gateway/service.
- If two SecurityWebFilterChain beans both match a request, which one runs?Only the first by @Order that matches; later chains are skipped entirely. So a broad matcher on a low-order chain can unintentionally shadow a more specific higher-order one.
- How is the authenticated principal carried across thread hops in WebFlux?Via the Reactor Context (ReactiveSecurityContextHolder), which flows with the reactive subscription, rather than a ThreadLocal SecurityContextHolder as in the servlet stack.
saying these in an interview costs you the question
- Claiming WebFlux security uses servlet Filters / FilterChainProxy / SecurityFilterChain.
- Saying the principal is stored in a ThreadLocal SecurityContextHolder.
- Assuming all matching chains run instead of only the first matching one.
- Introducing blocking calls inside a security WebFilter.