How do you authenticate a WebTestClient request in a reactive (WebFlux) test using spring-security-test?
answer
- reactive = Reactor context, not ThreadLocal
- SecurityMockServerConfigurers + mutateWith
- mockUser/mockJwt/mockOpaqueToken/csrf
- apply(springSecurity()) to bind filters
- not the servlet MockMvc post-processors
basics
~10 sUse 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 sIn 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 linesimport 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
Know reactive tests use mutateWith(mockUser(...)) instead of MockMvc's .with(user(...)).
Explain the separate SecurityMockServerConfigurers class and that reactive security lives in the Reactor context.
Wire the client with apply(springSecurity()), use mockJwt/mockOpaqueToken for resource servers, and remember csrf().
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.