skip to content

How do you authenticate a WebTestClient request in a reactive (WebFlux) test using spring-security-test?

level: seniorimportance: should knowfreq 45%

answer

  1. reactive = Reactor context, not ThreadLocal
  2. SecurityMockServerConfigurers + mutateWith
  3. mockUser/mockJwt/mockOpaqueToken/csrf
  4. apply(springSecurity()) to bind filters
  5. not the servlet MockMvc post-processors

basics

~10 s

Use WebTestClient's mutateWith(...) with mutators from SecurityMockServerConfigurers, e.g. client.mutateWith(mockUser("alice").roles("ADMIN")). There are also mockJwt(), mockOpaqueToken(), and csrf() mutators. In WebFlux, authentication lives in the Reactor context, so these configure the exchange accordingly.

solid answer

~40 s

In WebFlux the SecurityContext isn't a ThreadLocal; it lives in the Reactor Context via ReactiveSecurityContextHolder. So @WithMockUser still works for method-level reactive tests (the listener writes a context), but for WebTestClient HTTP-style tests you use mutators from SecurityMockServerConfigurers applied with mutateWith(...): mockUser()/mockUser(UserDetails) sets an authenticated principal, mockAuthentication(auth) sets a specific Authentication, mockJwt() and mockOpaqueToken() cover resource-server endpoints, and csrf() adds a CSRF token for mutating requests. You can mutate the whole client once (client.mutateWith(...)) so every request shares the identity, or per-request via the returned spec. These configurers set up a WebSessionServerSecurityContextRepository / mock so the security filters see the seeded authentication without a real login. Static-import from SecurityMockServerConfigurers, not the servlet SecurityMockMvcRequestPostProcessors.

code

java · 31 lines
java
import static org.springframework.security.test.web.reactive.server
        .SecurityMockServerConfigurers.*;

@SpringBootTest
class OrderHandlerTest {

  WebTestClient client;

  @BeforeEach
  void setup(ApplicationContext ctx) {
    client = WebTestClient.bindToApplicationContext(ctx)
        .apply(springSecurity())         // wire the reactive security filter chain
        .configureClient().build();
  }

  @Test
  void createOrder_asAdmin() {
    client.mutateWith(mockUser("alice").roles("ADMIN"))
          .mutateWith(csrf())
          .post().uri("/api/orders").bodyValue("{}")
          .exchange().expectStatus().isCreated();
  }

  @Test
  void readOrder_withJwtScope() {
    client.mutateWith(mockJwt().authorities(
              new SimpleGrantedAuthority("SCOPE_orders:read")))
          .get().uri("/api/orders/1")
          .exchange().expectStatus().isOk();
  }
}

go deeper

for a junior

Know reactive tests use mutateWith(mockUser(...)) instead of MockMvc's .with(user(...)).

for a middle

Explain the separate SecurityMockServerConfigurers class and that reactive security lives in the Reactor context.

for a senior

Wire the client with apply(springSecurity()), use mockJwt/mockOpaqueToken for resource servers, and remember csrf().

for a principal

Reason about propagation semantics of ReactiveSecurityContextHolder and keep servlet vs reactive test utilities from being mixed across a codebase.

## Reactive is different Spring WebFlux is non-blocking, so security state cannot live in a `ThreadLocal`. Instead the authenticated `SecurityContext` propagates through the **Reactor `Context`** via `ReactiveSecurityContextHolder`. spring-security-test provides a reactive-specific configurer class: **`SecurityMockServerConfigurers`** (`org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers`). You apply its mutators through `WebTestClient`'s `mutateWith(...)`. ## The mutators Static-import `SecurityMockServerConfigurers.*`: - **`mockUser()` / `mockUser("alice")` / `mockUser(UserDetails)`** — seeds an authenticated principal (chain `.roles(...)`, `.authorities(...)`). Analogous to servlet `user()` / `@WithMockUser`. - **`mockAuthentication(Authentication)`** — seeds an exact `Authentication` you construct. - **`mockJwt()`** — seeds a `JwtAuthenticationToken` with a mock decoded `Jwt` for `oauth2ResourceServer().jwt()` endpoints (customize claims/authorities like the servlet `jwt()`). - **`mockOpaqueToken()`** — for introspection-based resource servers. - **`csrf()`** — adds a valid CSRF token so mutating requests pass the reactive `CsrfWebFilter`. ## Applying them: two scopes 1. **Whole client** — `WebTestClient authed = client.mutateWith(mockUser("alice").roles("ADMIN"));` every request from `authed` carries that identity. 2. **Per request** — `client.mutateWith(mockJwt()).get().uri("/x")...` for that call. ## @WithMockUser still applies Because the reactive test listener writes into the reactive context, `@WithMockUser` on a reactive `@Test` method works for tests that call your service/handler beans directly and read `ReactiveSecurityContextHolder`. For `WebTestClient` HTTP tests, prefer the `mutateWith` configurers so the security **filters** (not just method code) see the principal. ## Common mistakes / gotchas 1. **Wrong import** — using servlet `SecurityMockMvcRequestPostProcessors.user()` in a WebFlux test won't apply to `WebTestClient`; you need `SecurityMockServerConfigurers` and `mutateWith`. 2. **Forgetting csrf()** — reactive apps also enable CSRF by default; mutating requests need `csrf()`. 3. **Building the client** — bind it with security: `WebTestClient.bindToApplicationContext(context).apply(springSecurity()).configureClient().build()` (the `springSecurity()` `WebTestClientConfigurer`, also from `SecurityMockServerConfigurers`) wires the security filter chain into the test client. With `@SpringBootTest(webEnvironment = RANDOM_PORT)` + `@AutoConfigureWebTestClient` the filters are already present. 4. **mockJwt() doesn't validate** — same as servlet: no signature/issuer check. 5. **Authorities/scope prefixing** mirrors servlet rules (`ROLE_` for roles, `SCOPE_` from jwt scopes). ## When to use Any WebFlux endpoint test driven through `WebTestClient` that must run as an authenticated user or a resource-server token — reach for `mutateWith(mockUser()/mockJwt()/csrf())`.

  • Why can't reactive security use a ThreadLocal like the servlet SecurityContextHolder?
    WebFlux processes a request across multiple threads (event loop), so a ThreadLocal wouldn't follow the reactive pipeline. Security state is carried in the Reactor Context and read via ReactiveSecurityContextHolder, which the mutateWith configurers populate.
  • What does apply(springSecurity()) do when building the WebTestClient?
    It's the WebTestClientConfigurer from SecurityMockServerConfigurers that wires Spring Security's reactive filter chain into the test client, so the mockUser()/mockJwt()/csrf() mutators are actually enforced by the security filters.

context