CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-security--spring-security-test

Spring Security Test provides comprehensive testing utilities for Spring Security applications with mock authentication, security context testing, and web security testing features.

Pending
Overview
Eval results
Files

test-context-management.mddocs/

Test Context Management

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.

Capabilities

TestSecurityContextHolder

Core utility class for managing security contexts in test environments, providing thread-local context management optimized for testing scenarios.

/**
 * Thread-local SecurityContext management for testing environments
 * Provides isolation between test methods and threads
 */
public class TestSecurityContextHolder {
    
    /**
     * Set the SecurityContext for the current thread
     * @param context The SecurityContext to set
     */
    public static void setContext(SecurityContext context);
    
    /**
     * Get the current SecurityContext for this thread
     * @return The current SecurityContext or empty context if none set
     */
    public static SecurityContext getContext();
    
    /**
     * Clear the SecurityContext for the current thread
     * Should be called after each test to prevent context leakage
     */
    public static void clearContext();
    
    /**
     * Create and set SecurityContext with the provided Authentication
     * Convenience method that creates SecurityContext automatically
     * @param authentication The Authentication to set in a new SecurityContext
     */
    public static void setAuthentication(Authentication authentication);
}

Usage Examples:

@Test
public void testWithProgrammaticSecurityContext() {
    // Create custom authentication
    Authentication auth = new UsernamePasswordAuthenticationToken(
        "programmatic-user", 
        "password",
        Arrays.asList(new SimpleGrantedAuthority("ROLE_CUSTOM"))
    );
    
    // Set authentication in test context
    TestSecurityContextHolder.setAuthentication(auth);
    
    try {
        // Test code that requires security context
        SecurityContext context = TestSecurityContextHolder.getContext();
        assertThat(context.getAuthentication().getName()).isEqualTo("programmatic-user");
        
        // Test business logic that depends on security context
        String currentUser = getCurrentUsername(); // Your method
        assertThat(currentUser).isEqualTo("programmatic-user");
        
    } finally {
        // Always clean up
        TestSecurityContextHolder.clearContext();
    }
}

@Test
public void testWithCustomSecurityContext() {
    // Create custom SecurityContext
    SecurityContext customContext = SecurityContextHolder.createEmptyContext();
    
    // Create complex authentication with custom principal
    CustomUserPrincipal principal = new CustomUserPrincipal("custom-user", "department-A");
    Authentication auth = new UsernamePasswordAuthenticationToken(
        principal, 
        "credentials",
        Arrays.asList(
            new SimpleGrantedAuthority("ROLE_USER"),
            new SimpleGrantedAuthority("DEPT_A")
        )
    );
    
    customContext.setAuthentication(auth);
    TestSecurityContextHolder.setContext(customContext);
    
    try {
        // Test with custom context
        SecurityContext retrievedContext = TestSecurityContextHolder.getContext();
        assertThat(retrievedContext).isEqualTo(customContext);
        
        CustomUserPrincipal retrievedPrincipal = 
            (CustomUserPrincipal) retrievedContext.getAuthentication().getPrincipal();
        assertThat(retrievedPrincipal.getDepartment()).isEqualTo("department-A");
        
    } finally {
        TestSecurityContextHolder.clearContext();
    }
}

TestSecurityContextHolderStrategyAdapter

Adapter that integrates TestSecurityContextHolder with Spring Security's SecurityContextHolderStrategy interface.

/**
 * Adapter that bridges TestSecurityContextHolder to SecurityContextHolderStrategy
 * Enables TestSecurityContextHolder to work with SecurityContextHolder strategy pattern
 */
public class TestSecurityContextHolderStrategyAdapter implements SecurityContextHolderStrategy {
    
    /**
     * Clear the SecurityContext using TestSecurityContextHolder
     */
    void clearContext();
    
    /**
     * Get SecurityContext using TestSecurityContextHolder
     * @return The current SecurityContext
     */
    SecurityContext getContext();
    
    /**
     * Set SecurityContext using TestSecurityContextHolder
     * @param context The SecurityContext to set
     */
    void setContext(SecurityContext context);
    
    /**
     * Create empty SecurityContext
     * @return New empty SecurityContext instance
     */
    SecurityContext createEmptyContext();
}

Usage Examples:

@Test
public void testWithStrategyAdapter() {
    TestSecurityContextHolderStrategyAdapter strategy = 
        new TestSecurityContextHolderStrategyAdapter();
    
    // Use strategy pattern
    SecurityContext context = strategy.createEmptyContext();
    Authentication auth = new UsernamePasswordAuthenticationToken(
        "strategy-user", "password", 
        Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
    );
    context.setAuthentication(auth);
    
    strategy.setContext(context);
    
    try {
        SecurityContext retrievedContext = strategy.getContext();
        assertThat(retrievedContext.getAuthentication().getName())
            .isEqualTo("strategy-user");
    } finally {
        strategy.clearContext();
    }
}

Test Execution Listeners

WithSecurityContextTestExecutionListener

TestExecutionListener that processes @WithSecurityContext meta-annotations and manages security context lifecycle during test execution.

/**
 * TestExecutionListener that processes @WithSecurityContext annotations
 * Handles security context setup and cleanup during test execution
 * Order: 10000 (runs after most Spring TestExecutionListeners)
 */
public class WithSecurityContextTestExecutionListener implements TestExecutionListener {
    
    /**
     * Process @WithSecurityContext annotations before test method execution
     * Sets up SecurityContext based on annotation configuration
     */
    void beforeTestMethod(TestContext testContext) throws Exception;
    
    /**
     * Process @WithSecurityContext annotations before test execution  
     * Alternative timing based on TestExecutionEvent configuration
     */
    void beforeTestExecution(TestContext testContext) throws Exception;
    
    /**
     * Clean up SecurityContext after test method completion
     * Ensures no context leakage between tests
     */
    void afterTestMethod(TestContext testContext) throws Exception;
    
    /**
     * Clean up SecurityContext after test execution
     * Alternative cleanup timing based on configuration
     */
    void afterTestExecution(TestContext testContext) throws Exception;
}

ReactorContextTestExecutionListener

TestExecutionListener that sets up Reactor Context with SecurityContext for reactive testing scenarios.

/**
 * TestExecutionListener for reactive security context management
 * Integrates SecurityContext with Reactor Context for WebFlux testing
 * Order: 11000 (runs after WithSecurityContextTestExecutionListener)
 */
public class ReactorContextTestExecutionListener implements TestExecutionListener {
    
    /**
     * Set up Reactor Context with SecurityContext before test execution
     * Enables reactive components to access security context
     */
    void beforeTestExecution(TestContext testContext) throws Exception;
    
    /**
     * Clean up Reactor Context after test execution
     * Prevents context pollution between tests
     */
    void afterTestExecution(TestContext testContext) throws Exception;
}

DelegatingTestExecutionListener

Utility listener that delegates to other TestExecutionListeners based on configuration.

/**
 * TestExecutionListener that delegates to other listeners
 * Useful for conditional or dynamic listener management
 */
public class DelegatingTestExecutionListener implements TestExecutionListener {
    
    /**
     * Constructor with delegate listeners
     * @param delegates The TestExecutionListeners to delegate to
     */
    public DelegatingTestExecutionListener(TestExecutionListener... delegates);
    
    // Delegates all TestExecutionListener methods to configured delegates
}

Web Testing Utilities

WebTestUtils

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.

/**
 * Utility class for accessing and manipulating Spring Security infrastructure
 * components during testing. Provides access to SecurityContextRepository,
 * CsrfTokenRepository, and related components from HttpServletRequest.
 */
public abstract class WebTestUtils {
    
    /**
     * Get SecurityContextRepository from request's filter chain
     * @param request The HttpServletRequest to examine
     * @return SecurityContextRepository or default HttpSessionSecurityContextRepository
     */
    public static SecurityContextRepository getSecurityContextRepository(HttpServletRequest request);
    
    /**
     * Set SecurityContextRepository for request's filter chain
     * @param request The HttpServletRequest to modify
     * @param securityContextRepository The SecurityContextRepository to set
     */
    public static void setSecurityContextRepository(
        HttpServletRequest request,
        SecurityContextRepository securityContextRepository
    );
    
    /**
     * Get CsrfTokenRepository from request's filter chain
     * @param request The HttpServletRequest to examine
     * @return CsrfTokenRepository or default HttpSessionCsrfTokenRepository
     */
    public static CsrfTokenRepository getCsrfTokenRepository(HttpServletRequest request);
    
    /**
     * Get CsrfTokenRequestHandler from request's filter chain
     * @param request The HttpServletRequest to examine
     * @return CsrfTokenRequestHandler or default XorCsrfTokenRequestAttributeHandler
     */
    public static CsrfTokenRequestHandler getCsrfTokenRequestHandler(HttpServletRequest request);
    
    /**
     * Set CsrfTokenRepository for request's filter chain
     * @param request The HttpServletRequest to modify
     * @param repository The CsrfTokenRepository to set
     */
    public static void setCsrfTokenRepository(
        HttpServletRequest request, 
        CsrfTokenRepository repository
    );
}

Usage Examples:

@SpringBootTest
@AutoConfigureTestDatabase
public class WebInfrastructureTest {
    
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mockMvc;
    
    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())
            .build();
    }
    
    @Test
    public void testCustomSecurityContextRepository() throws Exception {
        // Create custom SecurityContextRepository for testing
        SecurityContextRepository customRepo = new HttpSessionSecurityContextRepository();
        
        mockMvc.perform(get("/test-endpoint")
                .requestAttr("customRepo", customRepo)
                .with(user("testuser")))
               .andDo(result -> {
                   HttpServletRequest request = result.getRequest();
                   
                   // Get current repository
                   SecurityContextRepository currentRepo = 
                       WebTestUtils.getSecurityContextRepository(request);
                   assertThat(currentRepo).isNotNull();
                   
                   // Set custom repository
                   WebTestUtils.setSecurityContextRepository(request, customRepo);
                   
                   // Verify change
                   SecurityContextRepository updatedRepo = 
                       WebTestUtils.getSecurityContextRepository(request);
                   assertThat(updatedRepo).isEqualTo(customRepo);
               })
               .andExpect(status().isOk());
    }
    
    @Test
    public void testCsrfTokenRepositoryAccess() throws Exception {
        mockMvc.perform(post("/csrf-endpoint")
                .with(csrf())
                .with(user("testuser")))
               .andDo(result -> {
                   HttpServletRequest request = result.getRequest();
                   
                   // Access CSRF token repository
                   CsrfTokenRepository tokenRepo = 
                       WebTestUtils.getCsrfTokenRepository(request);
                   assertThat(tokenRepo).isNotNull();
                   
                   // Access CSRF token request handler
                   CsrfTokenRequestHandler requestHandler = 
                       WebTestUtils.getCsrfTokenRequestHandler(request);
                   assertThat(requestHandler).isNotNull();
               })
               .andExpect(status().isOk());
    }
    
    @Test
    public void testCustomCsrfConfiguration() throws Exception {
        // Create custom CSRF token repository
        CsrfTokenRepository customCsrfRepo = new HttpSessionCsrfTokenRepository();
        
        mockMvc.perform(post("/custom-csrf")
                .with(csrf())
                .with(user("testuser")))
               .andDo(result -> {
                   HttpServletRequest request = result.getRequest();
                   
                   // Set custom CSRF repository
                   WebTestUtils.setCsrfTokenRepository(request, customCsrfRepo);
                   
                   // Verify the change
                   CsrfTokenRepository retrievedRepo = 
                       WebTestUtils.getCsrfTokenRepository(request);
                   assertThat(retrievedRepo).isEqualTo(customCsrfRepo);
               })
               .andExpect(status().isOk());
    }
}

Advanced Testing Patterns

Manual Context Management in Complex Tests

@SpringBootTest
public class ComplexSecurityContextTest {
    
    @Test
    public void testMultipleSecurityContexts() {
        // First context - regular user
        Authentication userAuth = new UsernamePasswordAuthenticationToken(
            "user", "password", 
            Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
        );
        TestSecurityContextHolder.setAuthentication(userAuth);
        
        try {
            // Test user operations
            String userResult = performUserOperation();
            assertThat(userResult).contains("user");
            
            // Switch to admin context
            Authentication adminAuth = new UsernamePasswordAuthenticationToken(
                "admin", "password",
                Arrays.asList(
                    new SimpleGrantedAuthority("ROLE_ADMIN"),
                    new SimpleGrantedAuthority("ROLE_USER")
                )
            );
            TestSecurityContextHolder.setAuthentication(adminAuth);
            
            // Test admin operations
            String adminResult = performAdminOperation();
            assertThat(adminResult).contains("admin");
            
        } finally {
            TestSecurityContextHolder.clearContext();
        }
    }
    
    @Test
    public void testSecurityContextInCallbacks() {
        // Set up context for async operations
        Authentication auth = new UsernamePasswordAuthenticationToken(
            "async-user", "password",
            Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
        );
        TestSecurityContextHolder.setAuthentication(auth);
        
        try {
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                // Context should be available in async callback
                SecurityContext context = TestSecurityContextHolder.getContext();
                return context.getAuthentication().getName();
            });
            
            String result = future.get(1, TimeUnit.SECONDS);
            assertThat(result).isEqualTo("async-user");
            
        } catch (Exception e) {
            fail("Async operation failed", e);
        } finally {
            TestSecurityContextHolder.clearContext();
        }
    }
}

Custom Test Execution Listener

/**
 * Custom TestExecutionListener for specialized security context management
 */
public class CustomSecurityTestExecutionListener implements TestExecutionListener {
    
    @Override
    public void beforeTestMethod(TestContext testContext) throws Exception {
        Method testMethod = testContext.getTestMethod();
        
        // Check for custom security annotation
        CustomSecurity customSecurity = testMethod.getAnnotation(CustomSecurity.class);
        if (customSecurity != null) {
            setupCustomSecurityContext(customSecurity);
        }
    }
    
    @Override
    public void afterTestMethod(TestContext testContext) throws Exception {
        // Always clean up
        TestSecurityContextHolder.clearContext();
    }
    
    private void setupCustomSecurityContext(CustomSecurity annotation) {
        // Create custom authentication based on annotation
        Authentication auth = createCustomAuthentication(
            annotation.username(), 
            annotation.permissions()
        );
        TestSecurityContextHolder.setAuthentication(auth);
    }
    
    private Authentication createCustomAuthentication(String username, String[] permissions) {
        List<GrantedAuthority> authorities = Arrays.stream(permissions)
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());
            
        return new UsernamePasswordAuthenticationToken(username, "password", authorities);
    }
}

// Usage with custom annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomSecurity {
    String username() default "testuser";
    String[] permissions() default {};
}

// Test class using custom listener
@SpringBootTest
@TestExecutionListeners(
    listeners = CustomSecurityTestExecutionListener.class,
    mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
public class CustomSecurityTest {
    
    @Test
    @CustomSecurity(username = "custom-user", permissions = {"READ", "WRITE"})
    public void testWithCustomSecurity() {
        SecurityContext context = TestSecurityContextHolder.getContext();
        assertThat(context.getAuthentication().getName()).isEqualTo("custom-user");
        
        Collection<? extends GrantedAuthority> authorities = 
            context.getAuthentication().getAuthorities();
        assertThat(authorities).hasSize(2);
        assertThat(authorities.stream().map(GrantedAuthority::getAuthority))
            .containsExactlyInAnyOrder("READ", "WRITE");
    }
}

Integration with MockMvc and WebTestClient

@SpringBootTest  
public class IntegratedContextManagementTest {
    
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mockMvc;
    
    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())
            .build();
    }
    
    @Test
    public void testMixedContextUsage() throws Exception {
        // Set up context programmatically
        Authentication auth = new UsernamePasswordAuthenticationToken(
            "mixed-user", "password",
            Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
        );
        TestSecurityContextHolder.setAuthentication(auth);
        
        try {
            // Use with MockMvc via testSecurityContext()
            mockMvc.perform(get("/user-info")
                    .with(testSecurityContext()))
                   .andExpect(status().isOk())
                   .andExpect(authenticated().withUsername("mixed-user"));
                   
            // Verify context is still available
            SecurityContext retrievedContext = TestSecurityContextHolder.getContext();
            assertThat(retrievedContext.getAuthentication().getName())
                .isEqualTo("mixed-user");
                
        } finally {
            TestSecurityContextHolder.clearContext();
        }
    }
}

Thread Safety and Isolation

@SpringBootTest
public class ThreadSafetyTest {
    
    @Test
    public void testContextIsolationBetweenThreads() throws Exception {
        // Main thread context
        TestSecurityContextHolder.setAuthentication(
            new UsernamePasswordAuthenticationToken("main-user", "password", 
                Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")))
        );
        
        try {
            // Verify main thread context
            assertThat(TestSecurityContextHolder.getContext().getAuthentication().getName())
                .isEqualTo("main-user");
            
            // Test in separate thread
            CompletableFuture<String> otherThreadResult = CompletableFuture.supplyAsync(() -> {
                // Other thread should have empty context
                SecurityContext otherContext = TestSecurityContextHolder.getContext();
                return otherContext.getAuthentication() != null ? 
                    otherContext.getAuthentication().getName() : "no-auth";
            });
            
            String result = otherThreadResult.get(1, TimeUnit.SECONDS);
            assertThat(result).isEqualTo("no-auth");
            
            // Main thread context should still be intact
            assertThat(TestSecurityContextHolder.getContext().getAuthentication().getName())
                .isEqualTo("main-user");
                
        } finally {
            TestSecurityContextHolder.clearContext();
        }
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-security--spring-security-test

docs

index.md

mockmvc-integration.md

reactive-testing.md

security-context-annotations.md

test-context-management.md

tile.json