How does the ServerHttpSecurity DSL differ from the servlet HttpSecurity DSL, and how do you write authorization rules with it?
answer
- authorizeExchange + pathMatchers
- ServerWebExchange, everything returns Mono
- order matters, anyExchange() last
- Customizer lambda style, .and() deprecated
- SecurityWebFiltersOrder for custom filters
basics
~10 sServerHttpSecurity is the reactive builder. You use authorizeExchange() with pathMatchers() to protect URLs, plus methods like httpBasic() and csrf(), then call build() to get a SecurityWebFilterChain. The servlet version uses authorizeHttpRequests() and requestMatchers().
solid answer
~30 sServerHttpSecurity is the reactive equivalent of HttpSecurity, but the vocabulary is 'exchange'-centric because WebFlux works with ServerWebExchange instead of HttpServletRequest. Authorization uses authorizeExchange(...) with pathMatchers(...) (or matchers taking a ServerWebExchangeMatcher), versus the servlet authorizeHttpRequests(...) with requestMatchers(...). Terminal rules are the same vocabulary — permitAll(), authenticated(), hasRole(), hasAuthority(), access(...) — but the reactive access(...) takes a ReactiveAuthorizationManager returning Mono. Feature methods mirror the servlet side: httpBasic(), formLogin(), oauth2ResourceServer(), csrf(), cors(), headers(), exceptionHandling(). Everything returns Mono under the hood so the chain never blocks a Netty event-loop thread. You end with .build() to produce the SecurityWebFilterChain bean.
code
java · 12 lines@Bean
SecurityWebFilterChain chain(ServerHttpSecurity http) {
return http
.authorizeExchange(exchange -> exchange
.pathMatchers(HttpMethod.POST, "/api/orders").hasAuthority("SCOPE_write")
.pathMatchers("/admin/**").hasRole("ADMIN")
.pathMatchers("/public/**").permitAll()
.anyExchange().authenticated())
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.httpBasic(Customizer.withDefaults())
.build();
}go deeper
Recognize authorizeExchange/pathMatchers as the reactive way to protect URLs.
Fluently map servlet DSL to reactive DSL and write ordered rules with feature blocks.
Explain reactive CSRF repositories, custom WebFilter placement, and access(ReactiveAuthorizationManager).
Reason about non-blocking constraints across the whole security chain and how misordering creates authz holes.
## Why a different builder exists WebFlux has no Servlet API. A request is a **ServerWebExchange** (wrapping `ServerHttpRequest`/`ServerHttpResponse`), and every step returns a reactive type (`Mono`/`Flux`) so nothing blocks the event loop. Spring Security therefore ships a parallel builder, **`ServerHttpSecurity`**, mirroring servlet `HttpSecurity` but reactive throughout. ## Naming map (servlet -> reactive) | Servlet (HttpSecurity) | Reactive (ServerHttpSecurity) | |---|---| | `authorizeHttpRequests` | `authorizeExchange` | | `requestMatchers(...)` | `pathMatchers(...)` | | `AuthorizationManager` | `ReactiveAuthorizationManager` | | `UserDetailsService` | `ReactiveUserDetailsService` | | `AuthenticationManager` | `ReactiveAuthenticationManager` | | `SecurityFilterChain` | `SecurityWebFilterChain` | | `Filter` | `WebFilter` | ## Authorization rules ```java http.authorizeExchange(exchange -> exchange .pathMatchers(HttpMethod.GET, "/api/**").permitAll() .pathMatchers("/admin/**").hasRole("ADMIN") .matchers(new PathPatternParserServerWebExchangeMatcher("/reports/**")) .hasAuthority("SCOPE_reports") .anyExchange().authenticated()); ``` Key points: - `pathMatchers(...)` accepts an optional `HttpMethod` plus Ant/path patterns. - **Order matters** — the first matching rule wins, so put specific rules before `anyExchange()`. - Terminal authorizations: `permitAll()`, `denyAll()`, `authenticated()`, `hasRole()` (prepends `ROLE_`), `hasAuthority()`, `hasAnyRole()`, `access(ReactiveAuthorizationManager)` for custom logic. - `anyExchange()` is the catch-all; forgetting it can leave endpoints implicitly permitted or, more dangerously, misordered. ## Feature blocks ```java http .httpBasic(Customizer.withDefaults()) .formLogin(Customizer.withDefaults()) .csrf(ServerHttpSecurity.CsrfSpec::disable) // e.g. for a stateless API .cors(Customizer.withDefaults()) .exceptionHandling(e -> e.authenticationEntryPoint(myEntryPoint)); ``` Each lambda receives a spec object (`HttpBasicSpec`, `FormLoginSpec`, `CsrfSpec`, ...). The lambda/Customizer style is now the standard; older chained `.and()` style is deprecated. ## CSRF and reactive tokens CSRF is on by default. Because the token must be read reactively, WebFlux uses `ServerCsrfTokenRepository` (e.g. `CookieServerCsrfTokenRepository.withHttpOnlyFalse()`), not the servlet repository. Disable CSRF only for stateless token-authenticated APIs. ## Custom filter placement Insert a custom `WebFilter` with `http.addFilterAt(myFilter, SecurityWebFiltersOrder.AUTHENTICATION)` or `addFilterBefore/After`, using the `SecurityWebFiltersOrder` enum instead of the servlet `SecurityFilterChain` position constants. ## Gotchas - Calling `authorizeHttpRequests`/`requestMatchers` in WebFlux won't compile — wrong builder. - `hasRole("ADMIN")` checks authority `ROLE_ADMIN`; store roles accordingly. - Reactive `access(...)` must return a `Mono<AuthorizationDecision>` and must not block. - Don't reintroduce blocking calls (JDBC, `RestTemplate`) inside custom reactive security components. ## When to use Always, in a WebFlux app — this is the only supported way to express authorization and enable auth mechanisms.
- Why does authorization order matter in authorizeExchange?Rules are evaluated top-down; the first matching pathMatcher decides. A broad rule placed before a specific one can shadow it, so specific patterns must come before anyExchange().
saying these in an interview costs you the question
- Using authorizeHttpRequests()/requestMatchers() in a WebFlux config (servlet-only API).
- Believing hasRole('ADMIN') matches authority 'ADMIN' rather than 'ROLE_ADMIN'.
- Putting blocking JDBC/RestTemplate calls inside a reactive authorization manager.