Developer-kit unit-test-security-authorization

Provides patterns for unit testing Spring Security with `@PreAuthorize`, `@Secured`, `@RolesAllowed`. Validates role-based access control and authorization policies. Use when testing security configurations and access control logic.

install
source · Clone the upstream repo
git clone https://github.com/giuseppe-trisciuoglio/developer-kit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/giuseppe-trisciuoglio/developer-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/developer-kit-java/skills/unit-test-security-authorization" ~/.claude/skills/giuseppe-trisciuoglio-developer-kit-unit-test-security-authorization && rm -rf "$T"
manifest: plugins/developer-kit-java/skills/unit-test-security-authorization/SKILL.md
source content

Unit Testing Security and Authorization

Overview

This skill provides patterns for unit testing Spring Security authorization logic using

@PreAuthorize
,
@Secured
,
@RolesAllowed
, and custom permission evaluators. It covers testing role-based access control (RBAC), expression-based authorization, custom permission evaluators, and verifying access denied scenarios without full Spring Security context.

When to Use

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

Instructions

Follow these steps to test Spring Security authorization:

1. Set Up Security Testing Dependencies

Add spring-security-test to your test dependencies:

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>

2. Enable Method Security in Test Configuration

@Configuration
@EnableMethodSecurity
class TestSecurityConfig { }

3. Test with
@WithMockUser

@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminAccess() {
  assertThatCode(() -> service.deleteUser(1L))
    .doesNotThrowAnyException();
}

@Test
@WithMockUser(roles = "USER")
void shouldDenyUserAccess() {
  assertThatThrownBy(() -> service.deleteUser(1L))
    .isInstanceOf(AccessDeniedException.class);
}

4. Test Custom Permission Evaluators

@Test
void shouldGrantPermissionToOwner() {
  Authentication auth = new UsernamePasswordAuthenticationToken(
    "alice", null, List.of(new SimpleGrantedAuthority("ROLE_USER"))
  );
  Document doc = new Document(1L, "Test", new User("alice"));

  boolean result = evaluator.hasPermission(auth, doc, "WRITE");
  assertThat(result).isTrue();
}

5. Validate Security is Active

If tests pass unexpectedly, add this assertion to verify security is enforced:

@Test
void shouldRejectUnauthorizedWhenSecurityEnabled() {
  assertThatThrownBy(() -> service.deleteUser(1L))
    .isInstanceOf(AccessDeniedException.class);
}

Quick Reference

AnnotationDescriptionExample
@PreAuthorize
Pre-invocation authorization
@PreAuthorize("hasRole('ADMIN')")
@PostAuthorize
Post-invocation authorization
@PostAuthorize("returnObject.owner == authentication.name")
@Secured
Simple role-based security
@Secured("ROLE_ADMIN")
@RolesAllowed
JSR-250 standard
@RolesAllowed({"ADMIN", "MANAGER"})
@WithMockUser
Test annotation
@WithMockUser(roles = "ADMIN")

Examples

Basic
@PreAuthorize
Test

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

// Test
@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminToDeleteUser() {
  assertThatCode(() -> service.deleteUser(1L))
    .doesNotThrowAnyException();
}

@Test
@WithMockUser(roles = "USER")
void shouldDenyUserFromDeletingUser() {
  assertThatThrownBy(() -> service.deleteUser(1L))
    .isInstanceOf(AccessDeniedException.class);
}

Expression-Based Security Test

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

// For custom principal properties, use @WithUserDetails with a custom UserDetailsService
@Test
@WithUserDetails("alice")
void shouldAllowUserToAccessOwnProfile() {
  assertThatCode(() -> service.getUserProfile(1L))
    .doesNotThrowAnyException();
}

Validation tip: If a security test passes unexpectedly, verify that

@EnableMethodSecurity
is active on the test configuration — a missing annotation causes all
@PreAuthorize
checks to be bypassed silently.

See references/basic-testing.md for more basic patterns and references/advanced-authorization.md for complex expressions and custom evaluators.

Best Practices

  1. Use
    @WithMockUser
    for setting authenticated user context
  2. Test both allow and deny cases for each security rule
  3. Test with different roles to verify role-based decisions
  4. Test expression-based security comprehensively
  5. Mock external dependencies (permission evaluators, etc.)
  6. Test anonymous access separately from authenticated access
  7. 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

Constraints and Warnings

  • Method security requires proxy:
    @PreAuthorize
    works via proxies; direct method calls bypass security
  • @EnableGlobalMethodSecurity
    : Must be enabled for
    @PreAuthorize
    ,
    @Secured
    to work
  • Role prefix: Spring adds "ROLE_" prefix automatically; use
    hasRole('ADMIN')
    not
    hasRole('ROLE_ADMIN')
  • Authentication context: Security context is thread-local; be careful with async tests
  • @WithMockUser
    limitations
    : Creates a simple Authentication; complex auth scenarios need custom setup
  • SpEL expressions: Complex SpEL in
    @PreAuthorize
    can be difficult to debug; test thoroughly
  • Performance impact: Method security adds overhead; consider security at layer boundaries

References

Setup and Configuration

Testing Patterns

Advanced Topics

Complete Examples