| name | unit-test-security-authorization |
| description | Unit tests for Spring Security with @PreAuthorize, @Secured, @RolesAllowed. Test role-based access control and authorization policies. Use when validating security configurations and access control logic. |
| category | testing |
| tags | ["junit-5","spring-security","authorization","roles","preauthorize","mockmvc"] |
| version | 1.0.1 |
Unit Testing Security and Authorization
Test Spring Security authorization logic using @PreAuthorize, @Secured, and custom permission evaluators. Verify access control decisions without full security infrastructure.
When to Use This Skill
Use this skill when:
- Testing @PreAuthorize and @Secured method-level security
- Testing role-based access control (RBAC)
- Testing custom permission evaluators
- Verifying access denied scenarios
- Testing authorization with authenticated principals
- Want fast authorization tests without full Spring Security context
Setup: Security Testing
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Gradle
dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
Basic Pattern: Testing @PreAuthorize
Simple Role-Based Access Control
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long userId) {
}
@PreAuthorize("hasRole('USER')")
public User getCurrentUser() {
}
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public List<User> listAllUsers() {
}
}
import org.junit.jupiter.api.Test;
import org.springframework.security.test.context.support.WithMockUser;
import static org.assertj.core.api.Assertions.*;
class UserServiceSecurityTest {
@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminToDeleteUser() {
UserService service = new UserService();
assertThatCode(() -> service.deleteUser(1L))
.doesNotThrowAnyException();
}
@Test
@WithMockUser(roles = "USER")
void shouldDenyUserFromDeletingUser() {
UserService service = ();
assertThatThrownBy(() -> service.deleteUser())
.isInstanceOf(AccessDeniedException.class);
}
{
();
assertThatCode(() -> service.listAllUsers())
.doesNotThrowAnyException();
}
{
();
assertThatThrownBy(() -> service.deleteUser())
.isInstanceOf(AccessDeniedException.class);
}
}
Testing @Secured Annotation
Legacy Security Configuration
@Service
public class OrderService {
@Secured("ROLE_ADMIN")
public Order approveOrder(Long orderId) {
}
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public List<Order> getOrders() {
}
}
class OrderSecurityTest {
@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminToApproveOrder() {
OrderService service = new OrderService();
assertThatCode(() -> service.approveOrder(1L))
.doesNotThrowAnyException();
}
@Test
@WithMockUser(roles = "USER")
void shouldDenyUserFromApprovingOrder() {
OrderService service = new OrderService();
assertThatThrownBy(() -> service.approveOrder(1L))
.isInstanceOf(AccessDeniedException.class);
}
}
Testing Controller Security with MockMvc
Secure REST Endpoints
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@GetMapping("/users")
@PreAuthorize("hasRole('ADMIN')")
public List<UserDto> listAllUsers() {
}
@DeleteMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(@PathVariable Long id) {
}
}
import org.springframework.security.test.context.support.WithMockUser;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
class AdminControllerSecurityTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(new AdminController())
.apply(springSecurity())
.build();
}
@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminToListUsers() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
Exception {
mockMvc.perform(get())
.andExpect(status().isForbidden());
}
Exception {
mockMvc.perform(get())
.andExpect(status().isUnauthorized());
}
Exception {
mockMvc.perform(delete())
.andExpect(status().isOk());
}
}
Testing Expression-Based Authorization
Complex Permission Expressions
@Service
public class DocumentService {
@PreAuthorize("hasRole('ADMIN') or authentication.principal.username == #owner")
public Document getDocument(String owner, Long docId) {
}
@PreAuthorize("hasPermission(#docId, 'Document', 'WRITE')")
public void updateDocument(Long docId, String content) {
}
@PreAuthorize("#userId == authentication.principal.id")
public UserProfile getUserProfile(Long userId) {
}
}
class ExpressionBasedSecurityTest {
@Test
@WithMockUser(username = "alice", roles = "ADMIN")
void shouldAllowAdminToAccessAnyDocument() {
DocumentService service = new DocumentService();
assertThatCode(() -> service.getDocument("bob", 1L))
.doesNotThrowAnyException();
}
@Test
@WithMockUser(username = "alice")
void shouldAllowOwnerToAccessOwnDocument() {
DocumentService service = new DocumentService();
assertThatCode(() -> service.getDocument("alice", ))
.doesNotThrowAnyException();
}
{
();
assertThatThrownBy(() -> service.getDocument(, ))
.isInstanceOf(AccessDeniedException.class);
}
{
();
assertThatCode(() -> service.getUserProfile())
.doesNotThrowAnyException();
}
{
();
assertThatThrownBy(() -> service.getUserProfile())
.isInstanceOf(AccessDeniedException.class);
}
}
Testing Custom Permission Evaluator
Create and Test Custom Permission Logic
@Component
public class DocumentPermissionEvaluator implements PermissionEvaluator {
private final DocumentRepository documentRepository;
public DocumentPermissionEvaluator(DocumentRepository documentRepository) {
this.documentRepository = documentRepository;
}
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (authentication == null) return false;
Document document = (Document) targetDomainObject;
String userUsername = authentication.getName();
return document.getOwner().getUsername().equals(userUsername) ||
userHasRole(authentication, "ADMIN");
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
if (authentication == null) return false;
if (!"Document".equals(targetType)) return false;
Document document = documentRepository.findById((Long) targetId).orElse();
(document == ) ;
hasPermission(authentication, document, permission);
}
{
authentication.getAuthorities().stream()
.anyMatch(auth -> auth.getAuthority().equals( + role));
}
}
{
DocumentPermissionEvaluator evaluator;
DocumentRepository documentRepository;
Authentication adminAuth;
Authentication userAuth;
Document document;
{
documentRepository = mock(DocumentRepository.class);
evaluator = (documentRepository);
document = (, , ());
adminAuth = (
,
,
List.of( ())
);
userAuth = (
,
,
List.of( ())
);
}
{
evaluator.hasPermission(userAuth, document, );
assertThat(hasPermission).isTrue();
}
{
(
,
,
List.of( ())
);
evaluator.hasPermission(otherUserAuth, document, );
assertThat(hasPermission).isFalse();
}
{
evaluator.hasPermission(adminAuth, document, );
assertThat(hasPermission).isTrue();
}
{
evaluator.hasPermission(, document, );
assertThat(hasPermission).isFalse();
}
}
Testing Multiple Roles
Parameterized Role Testing
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class RoleBasedAccessTest {
private AdminService service;
@BeforeEach
void setUp() {
service = new AdminService();
}
@ParameterizedTest
@ValueSource(strings = {"ADMIN", "SUPER_ADMIN", "SYSTEM"})
@WithMockUser(roles = "ADMIN")
void shouldAllowPrivilegedRolesToDeleteUser(String role) {
assertThatCode(() -> service.deleteUser(1L))
.doesNotThrowAnyException();
}
@ParameterizedTest
@ValueSource(strings = {"USER", "GUEST", "READONLY"})
void shouldDenyUnprivilegedRolesToDeleteUser(String role) {
assertThatThrownBy(() -> service.deleteUser(1L))
.isInstanceOf(AccessDeniedException.class);
}
}
Best Practices
- Use @WithMockUser for setting authenticated user context
- Test both allow and deny cases for each security rule
- Test with different roles to verify role-based decisions
- Test expression-based security comprehensively
- Mock external dependencies (permission evaluators, etc.)
- Test anonymous access separately from authenticated access
- Use @EnableGlobalMethodSecurity in configuration for method-level security
Common Pitfalls
- Forgetting to enable method security in test configuration
- Not testing both allow and deny scenarios
- Testing framework code instead of authorization logic
- Not handling null authentication in tests
- Mixing authentication and authorization tests unnecessarily
Troubleshooting
AccessDeniedException not thrown: Ensure @EnableGlobalMethodSecurity(prePostEnabled = true) is configured.
@WithMockUser not working: Verify Spring Security test dependencies are on classpath.
Custom PermissionEvaluator not invoked: Check @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true).
References