Back to Testing & Quality Assurance

unit-test-security-authorization

Spring Securityunit testingauthorizationaccess controlRBACJavaSpring Bootsecurity testing
282📄 MIT🕒 2026-06-15Source ↗

Install this skill

npx skills add giuseppe-trisciuoglio/developer-kit

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

This skill focuses on validating Spring Security authorization constraints within a unit or integration testing environment. It provides methodologies for verifying that method-level security annotations like @PreAuthorize and @Secured function correctly without needing to initialize an entire application server. By using the spring-security-test library, agents can inject mocked security contexts into test suites, ensuring that role-based access control (RBAC) and custom permission logic enforce intended restrictions. This approach accelerates the development cycle by isolating security rules from database or infrastructure layers, allowing for rapid assertion of success and failure conditions during service layer or controller execution. Agents gain the capability to confirm that unauthorized attempts result in proper AccessDeniedException triggers or appropriate HTTP status codes, maintaining consistent security enforcement across the codebase.

When to Use This Skill

  • Ensuring regular users cannot access administrative service methods
  • Testing complex permission logic across multiple user roles
  • Verifying that guest or unauthenticated users receive access denied exceptions
  • Validating controller endpoints respond with 403 status for unauthorized requests
  • Confirming secure method execution for managers versus standard staff

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • write a unit test for my spring security annotations
  • how do I test @PreAuthorize on my service class
  • verify method security with mock security context
  • test access denied exceptions in spring boot
  • mock user authentication for controller tests

Pro Tips

  • 💡Always mock external dependencies to isolate the authorization logic under test, ensuring focused security validation.
  • 💡Leverage Spring Security Test's `@WithMockUser` and `@WithUserDetails` to easily simulate different authenticated principals and roles.
  • 💡Combine these unit tests with broader integration tests to cover both method-level and overall web security configurations effectively.

What this skill does

  • Verify @PreAuthorize method-level security expressions
  • Validate @Secured annotation enforcement for specific roles
  • Simulate authenticated users with mock security contexts
  • Test custom permission evaluators in isolation
  • Validate REST endpoint access control using MockMvc

When not to use it

  • When testing complex OIDC or OAuth2 flow integrations
  • When full database-backed security configuration testing is required
  • When integration testing of external Identity Providers is needed

Example workflow

  1. Include spring-security-test dependency in build file
  2. Apply @WithMockUser to your test methods with specific roles
  3. Execute service methods using assertion libraries like AssertJ
  4. Validate that unauthorized access throws an AccessDeniedException
  5. Configure MockMvc with security support for controller testing
  6. Perform HTTP requests and assert appropriate response status codes

Prerequisites

  • Spring Boot application
  • Spring Security dependency
  • JUnit 5 or similar test framework

Pitfalls & limitations

  • !Failing to include the security test library in the test scope
  • !Forgetting that @WithMockUser does not trigger full security filters unless configured
  • !Misinterpreting exception types when security context is absent

FAQ

Do I need a running database to test security?
No, using mock security contexts allows you to test authorization logic entirely in memory without needing a database connection.
Why is AccessDeniedException not being thrown in my test?
Ensure that your test configuration actually applies the security context; verify that you have used the correct annotation or setup code in your MockMvc builder.
Can I test multiple roles at once?
Yes, @WithMockUser supports arrays for roles, allowing you to simulate users with multiple authorities.
Is this approach sufficient for production security audit?
No, this is for functional verification of your authorization rules. Full end-to-end and penetration testing should still be performed.

How it compares

This skill automates the validation of security policies using specific framework hooks, which is significantly more reliable and repeatable than manual testing or writing custom mocks for security principals.

Source & trust

282 stars📄 MIT🕒 Updated 2026-06-15
📄 Full skill instructions — original source: giuseppe-trisciuoglio/developer-kit
# 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 with security annotations
@Service
public class UserService {

@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long userId) {
// delete logic
}

@PreAuthorize("hasRole('USER')")
public User getCurrentUser() {
// get user logic
}

@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public List<User> listAllUsers() {
// list logic
}
}

// Unit test
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 = new UserService();

assertThatThrownBy(() -> service.deleteUser(1L))
.isInstanceOf(AccessDeniedException.class);
}

@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminAndManagerToListUsers() {
UserService service = new UserService();

assertThatCode(() -> service.listAllUsers())
.doesNotThrowAnyException();
}

@Test
void shouldDenyAnonymousUserAccess() {
UserService service = new UserService();

assertThatThrownBy(() -> service.deleteUser(1L))
.isInstanceOf(AccessDeniedException.class);
}
}


## Testing @Secured Annotation

### Legacy Security Configuration

@Service
public class OrderService {

@Secured("ROLE_ADMIN")
public Order approveOrder(Long orderId) {
// approval logic
}

@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public List<Order> getOrders() {
// get orders
}
}

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() {
// logic
}

@DeleteMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(@PathVariable Long id) {
// delete logic
}
}

// Testing with MockMvc
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());
}

@Test
@WithMockUser(roles = "USER")
void shouldDenyUserFromListingUsers() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden());
}

@Test
void shouldDenyAnonymousAccessToAdminEndpoint() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isUnauthorized());
}

@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminToDeleteUser() throws Exception {
mockMvc.perform(delete("/api/admin/users/1"))
.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) {
// get document
}

@PreAuthorize("hasPermission(#docId, 'Document', 'WRITE')")
public void updateDocument(Long docId, String content) {
// update logic
}

@PreAuthorize("#userId == authentication.principal.id")
public UserProfile getUserProfile(Long userId) {
// get profile
}
}

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", 1L))
.doesNotThrowAnyException();
}

@Test
@WithMockUser(username = "alice")
void shouldDenyUserAccessToOtherUserDocument() {
DocumentService service = new DocumentService();

assertThatThrownBy(() -> service.getDocument("bob", 1L))
.isInstanceOf(AccessDeniedException.class);
}

@Test
@WithMockUser(username = "alice", id = "1")
void shouldAllowUserToAccessOwnProfile() {
DocumentService service = new DocumentService();

assertThatCode(() -> service.getUserProfile(1L))
.doesNotThrowAnyException();
}

@Test
@WithMockUser(username = "alice", id = "1")
void shouldDenyUserAccessToOtherProfile() {
DocumentService service = new DocumentService();

assertThatThrownBy(() -> service.getUserProfile(999L))
.isInstanceOf(AccessDeniedException.class);
}
}


## Testing Custom Permission Evaluator

### Create and Test Custom Permission Logic

// Custom permission evaluator
@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(null);
if (document == null) return false;

return hasPermission(authentication, document, permission);
}

private boolean userHasRole(Authentication authentication, String role) {
return authentication.getAuthorities().stream()
.anyMatch(auth -> auth.getAuthority().equals("ROLE_" + role));
}
}

// Unit test for custom evaluator
class DocumentPermissionEvaluatorTest {

private DocumentPermissionEvaluator evaluator;
private DocumentRepository documentRepository;
private Authentication adminAuth;
private Authentication userAuth;
private Document document;

@BeforeEach
void setUp() {
documentRepository = mock(DocumentRepository.class);
evaluator = new DocumentPermissionEvaluator(documentRepository);

document = new Document(1L, "Test Doc", new User("alice"));

adminAuth = new UsernamePasswordAuthenticationToken(
"admin",
null,
List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))
);

userAuth = new UsernamePasswordAuthenticationToken(
"alice",
null,
List.of(new SimpleGrantedAuthority("ROLE_USER"))
);
}

@Test
void shouldGrantPermissionToDocumentOwner() {
boolean hasPermission = evaluator.hasPermission(userAuth, document, "WRITE");

assertThat(hasPermission).isTrue();
}

@Test
void shouldDenyPermissionToNonOwner() {
Authentication otherUserAuth = new UsernamePasswordAuthenticationToken(
"bob",
null,
List.of(new SimpleGrantedAuthority("ROLE_USER"))
);

boolean hasPermission = evaluator.hasPermission(otherUserAuth, document, "WRITE");

assertThat(hasPermission).isFalse();
}

@Test
void shouldGrantPermissionToAdmin() {
boolean hasPermission = evaluator.hasPermission(adminAuth, document, "WRITE");

assertThat(hasPermission).isTrue();
}

@Test
void shouldDenyNullAuthentication() {
boolean hasPermission = evaluator.hasPermission(null, document, "WRITE");

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

- [Spring Security Method Security](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#jc-method)
- [Spring Security Testing](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#test)
- [@WithMockUser Documentation](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/test/context/support/WithMockUser.html)

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/unit-test-security-authorization/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/giuseppe-trisciuoglio/developer-kit/unit-test-security-authorization/SKILL.md
  • Cursor: ~/.cursor/skills/giuseppe-trisciuoglio/developer-kit/unit-test-security-authorization/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/giuseppe-trisciuoglio/developer-kit/unit-test-security-authorization/SKILL.md

🚀 Install with CLI:
npx skills add giuseppe-trisciuoglio/developer-kit

Read the Master Guide: Mastering Agent Skills

Recommended Rules

View more rules

Recommended Workflows

View more workflows

Recommended MCP Servers

View more MCP servers

Take It Further

Maximize your productivity with these powerful resources

📋

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
📖

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid testing & quality assurance issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Testing & Quality Assurance and is published by Giuseppe Trisciuoglio, maintained in giuseppe-trisciuoglio/developer-kit.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.