Mechanically, how does `user()` (and friends) get a `SecurityContext` in front of the filter chain, and how does that interact with `@WithMockUser` and the `springSecurity()` configurer?
answer
- post processor stores SecurityContext on request via test SecurityContextRepository
- springSecurity() configurer inserts the filter chain — mandatory
- SecurityContextHolderFilter loads it into ThreadLocal at request time
- @WithMockUser = TestExecutionListener + TestSecurityContextHolder (static, whole method)
- per-request post processor overrides annotation; reactive uses Reactor Context
basics
~20 sThe post processor stores a SecurityContext on the request (via a test SecurityContextRepository) before the filters run. The security filter chain — added by springSecurity() — reads it and populates SecurityContextHolder for that request. @WithMockUser sets the context statically for the whole method; a per-request post processor overrides it.
solid answer
~40 s`SecurityMockMvcRequestPostProcessors` builds a `SecurityContext` (holding a chosen `Authentication`) and saves it into a request attribute using a test-scoped `SecurityContextRepository` — the shared support code is `SecurityContextRequestPostProcessorSupport`. For this to reach the app, the security filter chain must be wired into MockMvc via `SecurityMockMvcConfigurers.springSecurity()` (automatic under `@AutoConfigureMockMvc`). At request time the `SecurityContextHolderFilter`/`SecurityContextPersistenceFilter` loads that stored context into `SecurityContextHolder` (ThreadLocal), so `@PreAuthorize`, `authorizeHttpRequests`, and controller `Principal` all see the fake user. `@WithMockUser`/`@WithSecurityContext` work differently: a `TestExecutionListener` (`WithSecurityContextTestExecutionListener`) sets `TestSecurityContextHolder` before the test method — the `TestSecurityContextRepository` then feeds it into the same filter. Post processors run later, per request, so they override the annotation. Reactive mirrors this via `ReactiveSecurityContextHolder`.
code
java · 18 lines// Hand-built MockMvc MUST apply springSecurity() or nothing authenticates
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
// Static default for the whole method...
@Test
@WithMockUser(username = "default", roles = "USER")
void overrideDemo() throws Exception {
// ...but this single request overrides it dynamically:
mockMvc.perform(get("/admin").with(user("alice").roles("ADMIN")))
.andExpect(status().isOk());
// this one uses the @WithMockUser 'default' USER and is forbidden
mockMvc.perform(get("/admin"))
.andExpect(status().isForbidden());
}go deeper
Just know post processors make the request run as a fake user and springSecurity() must be present.
Explain that the context is stored on the request and loaded by the filter chain, not set directly on the holder.
Articulate the annotation (TestExecutionListener) vs post-processor (per-request) paths and their precedence.
Reason about the fidelity/isolation tradeoffs, the shared test SecurityContextRepository, silent no-op failure modes, and the reactive ReactiveSecurityContextHolder analogue.
## The problem being solved Authorization checks (`@PreAuthorize`, URL rules, `SecurityContextHolder.getContext().getAuthentication()`) read the current `SecurityContext`. To test them you must place a chosen `Authentication` into that context *for the duration of one request*, without going through a real login. `spring-security-test` offers two entry points — **annotations** (static, whole-method) and **request post processors / mutators** (dynamic, per-request) — that both converge on the same filter-based loading mechanism. ## Post processor path (dynamic) 1. `user("alice")` (or `jwt()`, `oauth2Login()`, `authentication(...)`, `securityContext(...)`) returns a `RequestPostProcessor`. These share `SecurityContextRequestPostProcessorSupport`, which knows how to save a `SecurityContext` onto the `MockHttpServletRequest`. 2. When `mockMvc.perform(...)` runs, the post processor executes and **stores the SecurityContext in a request attribute** via a test `SecurityContextRepository` (rather than an HTTP session). 3. The **security filter chain** must be present. `SecurityMockMvcConfigurers.springSecurity()` inserts a `springSecurityFilterChain` delegating filter into MockMvc. `@SpringBootTest` + `@AutoConfigureMockMvc` applies it automatically; a hand-built `webAppContextSetup` must `.apply(springSecurity())`. 4. During the request, `SecurityContextHolderFilter` (Spring Security 6+; formerly `SecurityContextPersistenceFilter`) asks the configured `SecurityContextRepository` for a context. In tests the repository is swapped so it returns the one the post processor stored, and the filter sets it on the ThreadLocal `SecurityContextHolder`. 5. Downstream, authorization filters and method security see an authenticated principal; the controller can inject `Principal`/`Authentication`. 6. After the request the ThreadLocal is cleared, so requests don't leak state. ## Annotation path (static) `@WithMockUser`, `@WithUserDetails`, `@WithSecurityContext`, `@WithAnonymousUser` are handled by `WithSecurityContextTestExecutionListener`, a Spring `TestExecutionListener`. Before the test method it creates the `SecurityContext` and puts it in `TestSecurityContextHolder`. The `TestSecurityContextRepository` (installed by `springSecurity()`) then surfaces that context to the same `SecurityContextHolderFilter` during any request in the method. So the annotation covers **every** request the test makes, statically. ## Interaction / precedence Both funnel through the test `SecurityContextRepository`. When a request also carries a post processor like `.with(user(...))`, the per-request context stored by the post processor **overrides** the annotation's context for that request — dynamic beats static. This lets you set a class/method default with `@WithMockUser` and override individual requests with `.with(...)`. ## `testSecurityContext()` There's an explicit post processor `testSecurityContext()` that bridges a `TestSecurityContextHolder`-populated context into a request when you're building requests in a context where the automatic wiring isn't present — rarely needed with modern auto-config but useful to know it exists. ## Reactive analogue In WebFlux there is no ThreadLocal. `SecurityMockServerConfigurers.springSecurity()` installs a `WebFilter` that reads the mutation attached by `mockUser()`/`mockJwt()`/etc. and writes the `SecurityContext` into the **Reactor `Context`** via `ReactiveSecurityContextHolder`. `@WithMockUser` in reactive tests likewise populates that reactive context. The static-vs-dynamic override semantics carry over. ## Why it matters at a design level - **Fidelity vs. speed:** post processors bypass real authentication (decoders, providers) but exercise the *authorization* half of the chain faithfully — the right tradeoff for testing access rules. - **The wiring requirement is the #1 silent failure:** without `springSecurity()`, both annotations and post processors no-op and everything runs anonymous, which can produce false-green tests (endpoints permit-all) or confusing 401/403s. - **Isolation:** because the context is per-request/per-method and cleared afterward, tests don't bleed authentication into one another — important for parallel/ordered execution.
- A method annotated `@WithMockUser` also uses `.with(user("alice").roles("ADMIN"))` on one request. Which authentication does that request run as, and why?As alice/ADMIN. The per-request post processor stores a SecurityContext that the filter loads, overriding the statically-set @WithMockUser context for that specific request — dynamic beats static.
- Why can both @WithMockUser and `.with(user(...))` end up doing nothing at all?If the security filter chain isn't wired into MockMvc (missing `SecurityMockMvcConfigurers.springSecurity()` in a hand-built client), no filter loads the stored/annotation context, so requests run anonymous.
- Where does the equivalent context live for a WebFlux test?In the Reactor Context via ReactiveSecurityContextHolder; the reactive `springSecurity()` configurer installs a WebFilter that reads the mutator and writes it there, since there's no ThreadLocal.
saying these in an interview costs you the question
- Claiming the post processor sets SecurityContextHolder directly (it stores it for the filter to load)
- Saying @WithMockUser and .with(user()) conflict unpredictably — dynamic per-request always wins
- Believing springSecurity() is optional under all setups (only auto-applied via @AutoConfigureMockMvc/@AutoConfigureWebTestClient)
- Thinking reactive tests use a ThreadLocal SecurityContextHolder