0
# Test Context Management
1
2
Low-level utilities for programmatic security context management and integration with Spring Test framework execution listeners. These utilities provide direct control over security contexts and enable advanced testing scenarios beyond declarative annotations.
3
4
## Capabilities
5
6
### TestSecurityContextHolder
7
8
Core utility class for managing security contexts in test environments, providing thread-local context management optimized for testing scenarios.
9
10
```java { .api }
11
/**
12
* Thread-local SecurityContext management for testing environments
13
* Provides isolation between test methods and threads
14
*/
15
public class TestSecurityContextHolder {
16
17
/**
18
* Set the SecurityContext for the current thread
19
* @param context The SecurityContext to set
20
*/
21
public static void setContext(SecurityContext context);
22
23
/**
24
* Get the current SecurityContext for this thread
25
* @return The current SecurityContext or empty context if none set
26
*/
27
public static SecurityContext getContext();
28
29
/**
30
* Clear the SecurityContext for the current thread
31
* Should be called after each test to prevent context leakage
32
*/
33
public static void clearContext();
34
35
/**
36
* Create and set SecurityContext with the provided Authentication
37
* Convenience method that creates SecurityContext automatically
38
* @param authentication The Authentication to set in a new SecurityContext
39
*/
40
public static void setAuthentication(Authentication authentication);
41
}
42
```
43
44
**Usage Examples:**
45
46
```java
47
@Test
48
public void testWithProgrammaticSecurityContext() {
49
// Create custom authentication
50
Authentication auth = new UsernamePasswordAuthenticationToken(
51
"programmatic-user",
52
"password",
53
Arrays.asList(new SimpleGrantedAuthority("ROLE_CUSTOM"))
54
);
55
56
// Set authentication in test context
57
TestSecurityContextHolder.setAuthentication(auth);
58
59
try {
60
// Test code that requires security context
61
SecurityContext context = TestSecurityContextHolder.getContext();
62
assertThat(context.getAuthentication().getName()).isEqualTo("programmatic-user");
63
64
// Test business logic that depends on security context
65
String currentUser = getCurrentUsername(); // Your method
66
assertThat(currentUser).isEqualTo("programmatic-user");
67
68
} finally {
69
// Always clean up
70
TestSecurityContextHolder.clearContext();
71
}
72
}
73
74
@Test
75
public void testWithCustomSecurityContext() {
76
// Create custom SecurityContext
77
SecurityContext customContext = SecurityContextHolder.createEmptyContext();
78
79
// Create complex authentication with custom principal
80
CustomUserPrincipal principal = new CustomUserPrincipal("custom-user", "department-A");
81
Authentication auth = new UsernamePasswordAuthenticationToken(
82
principal,
83
"credentials",
84
Arrays.asList(
85
new SimpleGrantedAuthority("ROLE_USER"),
86
new SimpleGrantedAuthority("DEPT_A")
87
)
88
);
89
90
customContext.setAuthentication(auth);
91
TestSecurityContextHolder.setContext(customContext);
92
93
try {
94
// Test with custom context
95
SecurityContext retrievedContext = TestSecurityContextHolder.getContext();
96
assertThat(retrievedContext).isEqualTo(customContext);
97
98
CustomUserPrincipal retrievedPrincipal =
99
(CustomUserPrincipal) retrievedContext.getAuthentication().getPrincipal();
100
assertThat(retrievedPrincipal.getDepartment()).isEqualTo("department-A");
101
102
} finally {
103
TestSecurityContextHolder.clearContext();
104
}
105
}
106
```
107
108
### TestSecurityContextHolderStrategyAdapter
109
110
Adapter that integrates TestSecurityContextHolder with Spring Security's SecurityContextHolderStrategy interface.
111
112
```java { .api }
113
/**
114
* Adapter that bridges TestSecurityContextHolder to SecurityContextHolderStrategy
115
* Enables TestSecurityContextHolder to work with SecurityContextHolder strategy pattern
116
*/
117
public class TestSecurityContextHolderStrategyAdapter implements SecurityContextHolderStrategy {
118
119
/**
120
* Clear the SecurityContext using TestSecurityContextHolder
121
*/
122
void clearContext();
123
124
/**
125
* Get SecurityContext using TestSecurityContextHolder
126
* @return The current SecurityContext
127
*/
128
SecurityContext getContext();
129
130
/**
131
* Set SecurityContext using TestSecurityContextHolder
132
* @param context The SecurityContext to set
133
*/
134
void setContext(SecurityContext context);
135
136
/**
137
* Create empty SecurityContext
138
* @return New empty SecurityContext instance
139
*/
140
SecurityContext createEmptyContext();
141
}
142
```
143
144
**Usage Examples:**
145
146
```java
147
@Test
148
public void testWithStrategyAdapter() {
149
TestSecurityContextHolderStrategyAdapter strategy =
150
new TestSecurityContextHolderStrategyAdapter();
151
152
// Use strategy pattern
153
SecurityContext context = strategy.createEmptyContext();
154
Authentication auth = new UsernamePasswordAuthenticationToken(
155
"strategy-user", "password",
156
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
157
);
158
context.setAuthentication(auth);
159
160
strategy.setContext(context);
161
162
try {
163
SecurityContext retrievedContext = strategy.getContext();
164
assertThat(retrievedContext.getAuthentication().getName())
165
.isEqualTo("strategy-user");
166
} finally {
167
strategy.clearContext();
168
}
169
}
170
```
171
172
## Test Execution Listeners
173
174
### WithSecurityContextTestExecutionListener
175
176
TestExecutionListener that processes `@WithSecurityContext` meta-annotations and manages security context lifecycle during test execution.
177
178
```java { .api }
179
/**
180
* TestExecutionListener that processes @WithSecurityContext annotations
181
* Handles security context setup and cleanup during test execution
182
* Order: 10000 (runs after most Spring TestExecutionListeners)
183
*/
184
public class WithSecurityContextTestExecutionListener implements TestExecutionListener {
185
186
/**
187
* Process @WithSecurityContext annotations before test method execution
188
* Sets up SecurityContext based on annotation configuration
189
*/
190
void beforeTestMethod(TestContext testContext) throws Exception;
191
192
/**
193
* Process @WithSecurityContext annotations before test execution
194
* Alternative timing based on TestExecutionEvent configuration
195
*/
196
void beforeTestExecution(TestContext testContext) throws Exception;
197
198
/**
199
* Clean up SecurityContext after test method completion
200
* Ensures no context leakage between tests
201
*/
202
void afterTestMethod(TestContext testContext) throws Exception;
203
204
/**
205
* Clean up SecurityContext after test execution
206
* Alternative cleanup timing based on configuration
207
*/
208
void afterTestExecution(TestContext testContext) throws Exception;
209
}
210
```
211
212
### ReactorContextTestExecutionListener
213
214
TestExecutionListener that sets up Reactor Context with SecurityContext for reactive testing scenarios.
215
216
```java { .api }
217
/**
218
* TestExecutionListener for reactive security context management
219
* Integrates SecurityContext with Reactor Context for WebFlux testing
220
* Order: 11000 (runs after WithSecurityContextTestExecutionListener)
221
*/
222
public class ReactorContextTestExecutionListener implements TestExecutionListener {
223
224
/**
225
* Set up Reactor Context with SecurityContext before test execution
226
* Enables reactive components to access security context
227
*/
228
void beforeTestExecution(TestContext testContext) throws Exception;
229
230
/**
231
* Clean up Reactor Context after test execution
232
* Prevents context pollution between tests
233
*/
234
void afterTestExecution(TestContext testContext) throws Exception;
235
}
236
```
237
238
### DelegatingTestExecutionListener
239
240
Utility listener that delegates to other TestExecutionListeners based on configuration.
241
242
```java { .api }
243
/**
244
* TestExecutionListener that delegates to other listeners
245
* Useful for conditional or dynamic listener management
246
*/
247
public class DelegatingTestExecutionListener implements TestExecutionListener {
248
249
/**
250
* Constructor with delegate listeners
251
* @param delegates The TestExecutionListeners to delegate to
252
*/
253
public DelegatingTestExecutionListener(TestExecutionListener... delegates);
254
255
// Delegates all TestExecutionListener methods to configured delegates
256
}
257
```
258
259
## Web Testing Utilities
260
261
### WebTestUtils
262
263
Utility class providing low-level access to security infrastructure components in servlet environments, useful for advanced testing scenarios that require direct manipulation of security filters.
264
265
```java { .api }
266
/**
267
* Utility class for accessing and manipulating Spring Security infrastructure
268
* components during testing. Provides access to SecurityContextRepository,
269
* CsrfTokenRepository, and related components from HttpServletRequest.
270
*/
271
public abstract class WebTestUtils {
272
273
/**
274
* Get SecurityContextRepository from request's filter chain
275
* @param request The HttpServletRequest to examine
276
* @return SecurityContextRepository or default HttpSessionSecurityContextRepository
277
*/
278
public static SecurityContextRepository getSecurityContextRepository(HttpServletRequest request);
279
280
/**
281
* Set SecurityContextRepository for request's filter chain
282
* @param request The HttpServletRequest to modify
283
* @param securityContextRepository The SecurityContextRepository to set
284
*/
285
public static void setSecurityContextRepository(
286
HttpServletRequest request,
287
SecurityContextRepository securityContextRepository
288
);
289
290
/**
291
* Get CsrfTokenRepository from request's filter chain
292
* @param request The HttpServletRequest to examine
293
* @return CsrfTokenRepository or default HttpSessionCsrfTokenRepository
294
*/
295
public static CsrfTokenRepository getCsrfTokenRepository(HttpServletRequest request);
296
297
/**
298
* Get CsrfTokenRequestHandler from request's filter chain
299
* @param request The HttpServletRequest to examine
300
* @return CsrfTokenRequestHandler or default XorCsrfTokenRequestAttributeHandler
301
*/
302
public static CsrfTokenRequestHandler getCsrfTokenRequestHandler(HttpServletRequest request);
303
304
/**
305
* Set CsrfTokenRepository for request's filter chain
306
* @param request The HttpServletRequest to modify
307
* @param repository The CsrfTokenRepository to set
308
*/
309
public static void setCsrfTokenRepository(
310
HttpServletRequest request,
311
CsrfTokenRepository repository
312
);
313
}
314
```
315
316
**Usage Examples:**
317
318
```java
319
@SpringBootTest
320
@AutoConfigureTestDatabase
321
public class WebInfrastructureTest {
322
323
@Autowired
324
private WebApplicationContext context;
325
326
private MockMvc mockMvc;
327
328
@BeforeEach
329
public void setup() {
330
mockMvc = MockMvcBuilders
331
.webAppContextSetup(context)
332
.apply(springSecurity())
333
.build();
334
}
335
336
@Test
337
public void testCustomSecurityContextRepository() throws Exception {
338
// Create custom SecurityContextRepository for testing
339
SecurityContextRepository customRepo = new HttpSessionSecurityContextRepository();
340
341
mockMvc.perform(get("/test-endpoint")
342
.requestAttr("customRepo", customRepo)
343
.with(user("testuser")))
344
.andDo(result -> {
345
HttpServletRequest request = result.getRequest();
346
347
// Get current repository
348
SecurityContextRepository currentRepo =
349
WebTestUtils.getSecurityContextRepository(request);
350
assertThat(currentRepo).isNotNull();
351
352
// Set custom repository
353
WebTestUtils.setSecurityContextRepository(request, customRepo);
354
355
// Verify change
356
SecurityContextRepository updatedRepo =
357
WebTestUtils.getSecurityContextRepository(request);
358
assertThat(updatedRepo).isEqualTo(customRepo);
359
})
360
.andExpect(status().isOk());
361
}
362
363
@Test
364
public void testCsrfTokenRepositoryAccess() throws Exception {
365
mockMvc.perform(post("/csrf-endpoint")
366
.with(csrf())
367
.with(user("testuser")))
368
.andDo(result -> {
369
HttpServletRequest request = result.getRequest();
370
371
// Access CSRF token repository
372
CsrfTokenRepository tokenRepo =
373
WebTestUtils.getCsrfTokenRepository(request);
374
assertThat(tokenRepo).isNotNull();
375
376
// Access CSRF token request handler
377
CsrfTokenRequestHandler requestHandler =
378
WebTestUtils.getCsrfTokenRequestHandler(request);
379
assertThat(requestHandler).isNotNull();
380
})
381
.andExpect(status().isOk());
382
}
383
384
@Test
385
public void testCustomCsrfConfiguration() throws Exception {
386
// Create custom CSRF token repository
387
CsrfTokenRepository customCsrfRepo = new HttpSessionCsrfTokenRepository();
388
389
mockMvc.perform(post("/custom-csrf")
390
.with(csrf())
391
.with(user("testuser")))
392
.andDo(result -> {
393
HttpServletRequest request = result.getRequest();
394
395
// Set custom CSRF repository
396
WebTestUtils.setCsrfTokenRepository(request, customCsrfRepo);
397
398
// Verify the change
399
CsrfTokenRepository retrievedRepo =
400
WebTestUtils.getCsrfTokenRepository(request);
401
assertThat(retrievedRepo).isEqualTo(customCsrfRepo);
402
})
403
.andExpect(status().isOk());
404
}
405
}
406
```
407
408
## Advanced Testing Patterns
409
410
### Manual Context Management in Complex Tests
411
412
```java
413
@SpringBootTest
414
public class ComplexSecurityContextTest {
415
416
@Test
417
public void testMultipleSecurityContexts() {
418
// First context - regular user
419
Authentication userAuth = new UsernamePasswordAuthenticationToken(
420
"user", "password",
421
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
422
);
423
TestSecurityContextHolder.setAuthentication(userAuth);
424
425
try {
426
// Test user operations
427
String userResult = performUserOperation();
428
assertThat(userResult).contains("user");
429
430
// Switch to admin context
431
Authentication adminAuth = new UsernamePasswordAuthenticationToken(
432
"admin", "password",
433
Arrays.asList(
434
new SimpleGrantedAuthority("ROLE_ADMIN"),
435
new SimpleGrantedAuthority("ROLE_USER")
436
)
437
);
438
TestSecurityContextHolder.setAuthentication(adminAuth);
439
440
// Test admin operations
441
String adminResult = performAdminOperation();
442
assertThat(adminResult).contains("admin");
443
444
} finally {
445
TestSecurityContextHolder.clearContext();
446
}
447
}
448
449
@Test
450
public void testSecurityContextInCallbacks() {
451
// Set up context for async operations
452
Authentication auth = new UsernamePasswordAuthenticationToken(
453
"async-user", "password",
454
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
455
);
456
TestSecurityContextHolder.setAuthentication(auth);
457
458
try {
459
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
460
// Context should be available in async callback
461
SecurityContext context = TestSecurityContextHolder.getContext();
462
return context.getAuthentication().getName();
463
});
464
465
String result = future.get(1, TimeUnit.SECONDS);
466
assertThat(result).isEqualTo("async-user");
467
468
} catch (Exception e) {
469
fail("Async operation failed", e);
470
} finally {
471
TestSecurityContextHolder.clearContext();
472
}
473
}
474
}
475
```
476
477
### Custom Test Execution Listener
478
479
```java
480
/**
481
* Custom TestExecutionListener for specialized security context management
482
*/
483
public class CustomSecurityTestExecutionListener implements TestExecutionListener {
484
485
@Override
486
public void beforeTestMethod(TestContext testContext) throws Exception {
487
Method testMethod = testContext.getTestMethod();
488
489
// Check for custom security annotation
490
CustomSecurity customSecurity = testMethod.getAnnotation(CustomSecurity.class);
491
if (customSecurity != null) {
492
setupCustomSecurityContext(customSecurity);
493
}
494
}
495
496
@Override
497
public void afterTestMethod(TestContext testContext) throws Exception {
498
// Always clean up
499
TestSecurityContextHolder.clearContext();
500
}
501
502
private void setupCustomSecurityContext(CustomSecurity annotation) {
503
// Create custom authentication based on annotation
504
Authentication auth = createCustomAuthentication(
505
annotation.username(),
506
annotation.permissions()
507
);
508
TestSecurityContextHolder.setAuthentication(auth);
509
}
510
511
private Authentication createCustomAuthentication(String username, String[] permissions) {
512
List<GrantedAuthority> authorities = Arrays.stream(permissions)
513
.map(SimpleGrantedAuthority::new)
514
.collect(Collectors.toList());
515
516
return new UsernamePasswordAuthenticationToken(username, "password", authorities);
517
}
518
}
519
520
// Usage with custom annotation
521
@Target(ElementType.METHOD)
522
@Retention(RetentionPolicy.RUNTIME)
523
public @interface CustomSecurity {
524
String username() default "testuser";
525
String[] permissions() default {};
526
}
527
528
// Test class using custom listener
529
@SpringBootTest
530
@TestExecutionListeners(
531
listeners = CustomSecurityTestExecutionListener.class,
532
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
533
)
534
public class CustomSecurityTest {
535
536
@Test
537
@CustomSecurity(username = "custom-user", permissions = {"READ", "WRITE"})
538
public void testWithCustomSecurity() {
539
SecurityContext context = TestSecurityContextHolder.getContext();
540
assertThat(context.getAuthentication().getName()).isEqualTo("custom-user");
541
542
Collection<? extends GrantedAuthority> authorities =
543
context.getAuthentication().getAuthorities();
544
assertThat(authorities).hasSize(2);
545
assertThat(authorities.stream().map(GrantedAuthority::getAuthority))
546
.containsExactlyInAnyOrder("READ", "WRITE");
547
}
548
}
549
```
550
551
### Integration with MockMvc and WebTestClient
552
553
```java
554
@SpringBootTest
555
public class IntegratedContextManagementTest {
556
557
@Autowired
558
private WebApplicationContext context;
559
560
private MockMvc mockMvc;
561
562
@BeforeEach
563
public void setup() {
564
mockMvc = MockMvcBuilders
565
.webAppContextSetup(context)
566
.apply(springSecurity())
567
.build();
568
}
569
570
@Test
571
public void testMixedContextUsage() throws Exception {
572
// Set up context programmatically
573
Authentication auth = new UsernamePasswordAuthenticationToken(
574
"mixed-user", "password",
575
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
576
);
577
TestSecurityContextHolder.setAuthentication(auth);
578
579
try {
580
// Use with MockMvc via testSecurityContext()
581
mockMvc.perform(get("/user-info")
582
.with(testSecurityContext()))
583
.andExpect(status().isOk())
584
.andExpect(authenticated().withUsername("mixed-user"));
585
586
// Verify context is still available
587
SecurityContext retrievedContext = TestSecurityContextHolder.getContext();
588
assertThat(retrievedContext.getAuthentication().getName())
589
.isEqualTo("mixed-user");
590
591
} finally {
592
TestSecurityContextHolder.clearContext();
593
}
594
}
595
}
596
```
597
598
### Thread Safety and Isolation
599
600
```java
601
@SpringBootTest
602
public class ThreadSafetyTest {
603
604
@Test
605
public void testContextIsolationBetweenThreads() throws Exception {
606
// Main thread context
607
TestSecurityContextHolder.setAuthentication(
608
new UsernamePasswordAuthenticationToken("main-user", "password",
609
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")))
610
);
611
612
try {
613
// Verify main thread context
614
assertThat(TestSecurityContextHolder.getContext().getAuthentication().getName())
615
.isEqualTo("main-user");
616
617
// Test in separate thread
618
CompletableFuture<String> otherThreadResult = CompletableFuture.supplyAsync(() -> {
619
// Other thread should have empty context
620
SecurityContext otherContext = TestSecurityContextHolder.getContext();
621
return otherContext.getAuthentication() != null ?
622
otherContext.getAuthentication().getName() : "no-auth";
623
});
624
625
String result = otherThreadResult.get(1, TimeUnit.SECONDS);
626
assertThat(result).isEqualTo("no-auth");
627
628
// Main thread context should still be intact
629
assertThat(TestSecurityContextHolder.getContext().getAuthentication().getName())
630
.isEqualTo("main-user");
631
632
} finally {
633
TestSecurityContextHolder.clearContext();
634
}
635
}
636
}
637
```