skip to content

How do you declare URL-level access rules in WebFlux using authorizeExchange?

level: middleimportance: must knowfreq 55%

answer

  1. @EnableWebFluxSecurity + SecurityWebFilterChain bean
  2. ServerHttpSecurity.authorizeExchange
  3. pathMatchers -> hasRole/authenticated/permitAll
  4. anyExchange() catch-all, first match wins
  5. MVC analogue: authorizeHttpRequests/anyRequest

basics

~10 s

Define a SecurityWebFilterChain bean built from ServerHttpSecurity, and call .authorizeExchange(ex -> ex.pathMatchers("/admin/**").hasRole("ADMIN").anyExchange().authenticated()). It's the WebFlux equivalent of MVC's authorizeHttpRequests.

solid answer

~40 s

In WebFlux you annotate a config with `@EnableWebFluxSecurity` and expose a `SecurityWebFilterChain` bean created from `ServerHttpSecurity` (usually the injected `http` builder). Access rules go in `authorizeExchange`, which matches on *exchanges* (the WebFlux term for a request/response). You match paths with `pathMatchers(HttpMethod, "/path/**")` or `matchers(...)`, then attach a rule: `.permitAll()`, `.authenticated()`, `.hasRole("ADMIN")`, `.hasAuthority("SCOPE_read")`, or `.access(ReactiveAuthorizationManager)` for custom logic. Rules are evaluated **top to bottom, first match wins**, so specific patterns must precede broad ones, and you finish with `.anyExchange().authenticated()` (or `denyAll`) as a catch-all. This is the reactive analogue of MVC's `authorizeHttpRequests` on `HttpSecurity`. The whole chain of rules is enforced by the reactive `AuthorizationWebFilter` before your handler runs.

code

java · 16 lines
java
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Bean
    SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
        return http
            .authorizeExchange(exchanges -> exchanges
                .pathMatchers("/actuator/health").permitAll()
                .pathMatchers(HttpMethod.GET, "/api/articles/**").permitAll()
                .pathMatchers("/admin/**").hasRole("ADMIN")
                .anyExchange().authenticated())
            .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
            .build();
    }
}

go deeper

for a junior

Recognize authorizeExchange + pathMatchers + a catch-all anyExchange().

for a middle

Should map every WebFlux API to its MVC counterpart and explain first-match ordering.

for a senior

Should discuss hasRole vs hasAuthority, .access() with ReactiveAuthorizationManager, and layering method security.

for a principal

Should reason about coarse-vs-fine authorization strategy and how the AuthorizationWebFilter enforces it non-blocking.

## The building blocks - **`@EnableWebFluxSecurity`** — turns on reactive web security (the WebFlux counterpart of `@EnableWebSecurity`). - **`ServerHttpSecurity`** — the reactive builder (counterpart of MVC's `HttpSecurity`). You get one injected and configure it. - **`SecurityWebFilterChain`** — the bean you return (counterpart of MVC's `SecurityFilterChain`). It bundles the matchers, authentication, and authorization filters. - **Exchange** — WebFlux models a request/response as a `ServerWebExchange`; hence `authorizeExchange` and `anyExchange` (versus MVC's `authorizeHttpRequests`/`anyRequest`). ## Declaring rules ```java @Configuration @EnableWebFluxSecurity class SecurityConfig { @Bean SecurityWebFilterChain chain(ServerHttpSecurity http) { return http .authorizeExchange(ex -> ex .pathMatchers("/public/**").permitAll() .pathMatchers(HttpMethod.POST, "/api/**").hasRole("WRITER") .pathMatchers("/admin/**").hasRole("ADMIN") .anyExchange().authenticated()) .httpBasic(Customizer.withDefaults()) .build(); } } ``` ### Matchers - `pathMatchers("/x/**")` — Ant-style path match. - `pathMatchers(HttpMethod.POST, "/x")` — method + path. - `matchers(ServerWebExchangeMatcher...)` — arbitrary matchers. ### Rule verbs - `permitAll()` / `denyAll()` - `authenticated()` / `anonymous()` - `hasRole("ADMIN")` (adds the `ROLE_` prefix) vs `hasAuthority("ROLE_ADMIN")` (exact) - `hasAnyRole(...)`, `hasAnyAuthority(...)` - `access(ReactiveAuthorizationManager<AuthorizationContext>)` — custom, returns a Mono decision ## Ordering: first match wins Rules are evaluated in declaration order and the **first matching pattern decides**. So `/admin/**` must be listed before a broad `anyExchange()`; otherwise the catch-all would swallow it. Always end with an explicit `anyExchange()` (typically `.authenticated()` or `.denyAll()`) so nothing is unintentionally open. ## How it's enforced The configuration produces an `AuthorizationWebFilter` in the reactive filter chain. On each request it evaluates the matched rule against the `Authentication` in the Reactor Context (populated earlier by `AuthenticationWebFilter`) and returns `403`/`401` reactively if denied — all without blocking a thread. ## Gotchas - **Don't confuse APIs:** `authorizeHttpRequests`/`anyRequest`/`SecurityFilterChain` are MVC; `authorizeExchange`/`anyExchange`/`SecurityWebFilterChain` are WebFlux. Mixing them won't compile against the wrong builder. - **`hasRole` vs `hasAuthority`:** `hasRole("ADMIN")` checks authority `ROLE_ADMIN`. If your tokens carry `SCOPE_...`, use `hasAuthority("SCOPE_...")`. - **Order matters** — broad-before-specific is the classic bug. - URL rules are coarse; combine with method security (`@EnableReactiveMethodSecurity` + `@PreAuthorize`) for fine-grained checks. ## When to use Use `authorizeExchange` for coarse, path-based gating (public vs admin vs authenticated areas). Layer method security on top for domain-specific authorization.

  • What are the WebFlux equivalents of MVC's HttpSecurity, authorizeHttpRequests, anyRequest, and SecurityFilterChain?
    ServerHttpSecurity, authorizeExchange, anyExchange, and SecurityWebFilterChain respectively. WebFlux says 'exchange' because a request/response is a ServerWebExchange.
  • Why must specific pathMatchers come before anyExchange()?
    Rules are evaluated top-to-bottom and the first matching rule wins. anyExchange() matches everything, so if placed first it would apply its rule to all requests and shadow the more specific ones below it.

saying these in an interview costs you the question

  • Using authorizeHttpRequests/anyRequest (MVC API) in a WebFlux config
  • Putting anyExchange() before specific pathMatchers
  • Thinking hasRole('ADMIN') matches an authority literally named 'ADMIN' rather than 'ROLE_ADMIN'
  • Forgetting a catch-all rule, leaving unmatched paths open

context