Compare @WithMockUser, @WithUserDetails, and @WithSecurityContext. When do you need each?
answer
- same listener, different Authentication source
- MockUser=literal attrs, generic User principal
- WithUserDetails=loadUserByUsername, real principal
- WithSecurityContext=custom factory, any Authentication
- need custom principal type? not MockUser
basics
~20 s@WithMockUser fabricates a simple principal from annotation attributes. @WithUserDetails loads a real principal via your UserDetailsService bean by username, so the principal is your actual UserDetails type. @WithSecurityContext is the extensible base that lets you build a fully custom SecurityContext/Authentication via a factory.
solid answer
~40 sAll three seed the SecurityContext through the same WithSecurityContextTestExecutionListener but differ in how the principal is built. @WithMockUser creates a UsernamePasswordAuthenticationToken from literal attributes (username/roles/authorities) — fast, but the principal is a generic User, not your domain type. @WithUserDetails("alice") calls your registered UserDetailsService.loadUserByUsername, so the principal is your real UserDetails implementation with real authorities — use it when the controller casts to a custom principal or when you want authorities to come from the same source as production; it needs the user to exist (often seeded in a test @Configuration). @WithSecurityContext is the meta-annotation both are built on: you write a custom annotation plus a SecurityContextFactory that returns any Authentication (e.g. a JwtAuthenticationToken or OAuth2 principal) — use it for bespoke authentication types the built-ins don't cover.
code
java · 23 lines// Custom principal via @WithUserDetails (real UserDetailsService)
@Test
@WithUserDetails("[email protected]")
void usesRealPrincipal() throws Exception {
mvc.perform(get("/api/tenant").with(csrf()))
.andExpect(status().isOk());
}
// Fully custom Authentication via @WithSecurityContext
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockTenantSecurityContextFactory.class)
@interface WithMockTenant { String user(); long tenantId(); }
class WithMockTenantSecurityContextFactory
implements WithSecurityContextFactory<WithMockTenant> {
@Override public SecurityContext createSecurityContext(WithMockTenant a) {
var ctx = SecurityContextHolder.createEmptyContext();
var principal = new TenantPrincipal(a.user(), a.tenantId());
ctx.setAuthentication(new UsernamePasswordAuthenticationToken(
principal, null, List.of(new SimpleGrantedAuthority("ROLE_USER"))));
return ctx;
}
}go deeper
Know @WithMockUser is the simple default; the others exist for real/custom principals.
Explain that @WithUserDetails calls loadUserByUsername and yields your real principal type.
Build a custom @WithSecurityContext factory and pick correctly among the three based on principal type and authority source.
Set team conventions: prefer @WithUserDetails/custom factories so tested authorities match production mapping; avoid authority drift from hand-written @WithMockUser roles.
## Shared mechanism All three are (or are built on) `@WithSecurityContext` and are processed by the same `WithSecurityContextTestExecutionListener`, which builds a `SecurityContext` before the test method and clears it after. They differ only in **how the `Authentication` inside that context is produced**. ## @WithMockUser - Source: literal annotation attributes (`username`, `roles`, `authorities`, `password`). - Principal: a Spring Security `User` (implements `UserDetails`) inside a `UsernamePasswordAuthenticationToken`. - Pros: zero setup, no beans required, fastest. - Cons: principal is generic. If your controller does `((MyUserPrincipal) auth.getPrincipal()).getTenantId()` it will `ClassCastException`. Authorities are hand-declared, so they can drift from what production actually grants. ## @WithUserDetails - `@WithUserDetails("alice")` — looks up bean of type `UserDetailsService` (by default a bean named per `userDetailsServiceBeanName`, else by type) and calls `loadUserByUsername("alice")`. - Principal: **your real `UserDetails`** with the authorities your production logic assigns. - Requires the user to be resolvable — commonly you provide an in-memory or test `UserDetailsService` bean, or seed the DB. If not found, the test fails with `UsernameNotFoundException`. - `setupBefore` attribute (like `@WithMockUser`) controls whether the context is set before or after `@BeforeEach`. - Use when: custom principal type, or you want authorities sourced identically to production, or you're testing logic that depends on real user fields. ## @WithSecurityContext - The extension point. You create a **composed annotation** meta-annotated with `@WithSecurityContext(factory = MyFactory.class)`, and implement `WithSecurityContextFactory<YourAnnotation>` whose `createSecurityContext(annotation)` returns any `SecurityContext`. - Lets you seed **arbitrary** `Authentication` types: `JwtAuthenticationToken`, `OAuth2AuthenticationToken`, a custom token carrying tenant/claims, etc. - Use when the built-ins can't express your principal, and you want it declaratively/reusably (vs. the imperative `jwt()`/`user(UserDetails)` post-processors which do similar things per-request). ## Choosing - Simple role check, don't care about principal type → **@WithMockUser**. - Need the real domain principal / production authority mapping → **@WithUserDetails**. - Need a non-standard `Authentication` (JWT/OAuth2/custom) declaratively → **@WithSecurityContext** (custom annotation) — or use `jwt()`/`user(UserDetails)` post-processors imperatively. ## Gotchas 1. `@WithUserDetails` needs the `UserDetailsService` bean in the test's ApplicationContext; in a sliced `@WebMvcTest` you may have to provide one. 2. `@WithMockUser` roles auto-prefix `ROLE_`; `@WithUserDetails` uses whatever your service returns — no re-prefixing. 3. All three seed the context but still don't add a CSRF token — mutating MockMvc requests need `.with(csrf())`. 4. The factory in `@WithSecurityContext` must be a no-arg-constructable class (Spring can autowire it if `WithSecurityContextFactory` is a bean via `@TestExecutionListeners` support in newer versions, but the classic contract is a plain factory). 5. `@WithUserDetails` transaction/lazy-loading: if `loadUserByUsername` touches lazy JPA associations outside a transaction it can fail — keep the UserDetails self-contained.
- Your controller casts the principal to a custom UserDetails subtype and the @WithMockUser test throws ClassCastException. Fix?@WithMockUser injects a generic Spring User. Switch to @WithUserDetails so your real UserDetailsService returns your subtype, or use SecurityMockMvcRequestPostProcessors.user(myUserDetails), or a custom @WithSecurityContext factory that builds the exact principal type.
- What does @WithUserDetails need in the test context that @WithMockUser does not?A UserDetailsService bean that can resolve the given username (loadUserByUsername). In a sliced @WebMvcTest you often must supply one; @WithMockUser needs no beans since it fabricates the principal from attributes.