skip to content

How does the ServerHttpSecurity DSL differ from the servlet HttpSecurity DSL, and how do you write authorization rules with it?

level: middleimportance: should knowfreq 55%

answer

  1. authorizeExchange + pathMatchers
  2. ServerWebExchange, everything returns Mono
  3. order matters, anyExchange() last
  4. Customizer lambda style, .and() deprecated
  5. SecurityWebFiltersOrder for custom filters

basics

~10 s

ServerHttpSecurity 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 s

ServerHttpSecurity 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
java
@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

for a junior

Recognize authorizeExchange/pathMatchers as the reactive way to protect URLs.

for a middle

Fluently map servlet DSL to reactive DSL and write ordered rules with feature blocks.

for a senior

Explain reactive CSRF repositories, custom WebFilter placement, and access(ReactiveAuthorizationManager).

for a principal

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.

context