Skip to main content
skill CX Codex

unit-test-security-authorization — Claude Code skill for Spring

A skill with patterns for unit testing Spring Security authorization: @PreAuthorize, @Secured, @RolesAllowed and custom permission evaluators. Useful when you need fast access-control tests without the full Spring Security context.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-security-authorization -a claude-code
Open on GitHub

What unit-test-security-authorization does

The skill walks through testing method-level security in Spring. Steps: add spring-security-test to test dependencies, enable @EnableMethodSecurity in the test configuration, then check access with @WithMockUser under different roles. Examples cover both the allowed and denied paths with assertThatCode and assertThatThrownBy, plus custom permission evaluators.

Expression-based rules get their own section: @PreAuthorize("#userId == authentication.principal.id") and @WithUserDetails with a custom UserDetailsService. A quick reference table lists @PreAuthorize, @PostAuthorize, @Secured, @RolesAllowed and @WithMockUser with examples. Reference files in the repo cover setup, basic patterns, complex expressions and complete examples.

Here is the trap worth knowing about. If @EnableMethodSecurity is missing from the test configuration, every @PreAuthorize check is bypassed silently and the test passes when it should fail. The skill suggests adding a test that expects AccessDeniedException to confirm security is actually enforced. Also remember that @PreAuthorize works through proxies, so calling the method directly in a test skips the check.

What it helps with

  • verify that @PreAuthorize lets only admins through
  • write a denied-access test for the USER role
  • test a custom permission evaluator against the document owner
  • confirm method security is actually enabled in tests
  • cover SpEL expressions inside access annotations

Example prompts for the agent

  • write tests for @PreAuthorize on UserService.deleteUser with ADMIN and USER roles
  • check my custom permission evaluator: owner gets WRITE, everyone else does not
  • add a test that fails when @EnableMethodSecurity is missing

How to install unit-test-security-authorization

1Skills CLI (Vercel)

npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-security-authorization -a claude-code

2Manually: put the files in place

~/.claude/skills/unit-test-security-authorization/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/unit-test-security-authorization/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about unit-test-security-authorization

How do I install unit-test-security-authorization in Claude?

Run npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-security-authorization -a claude-code. The skill then lands at ~/.claude/skills/unit-test-security-authorization/SKILL.md.

What does unit-test-security-authorization do?

It provides unit testing patterns for Spring Security authorization: @PreAuthorize, @Secured, @RolesAllowed, custom permission evaluators and access-denied scenarios.

Why does my @PreAuthorize test pass when access should be denied?

Most likely @EnableMethodSecurity is missing from the test configuration, so the checks are bypassed silently. The skill recommends a test that expects AccessDeniedException.

Source text

SKILL.md · MIT
---
name: unit-test-security-authorization
description: 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.
allowed-tools: Read, Write, Bash, Glob, Grep
---

# 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:

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

### 2. Enable Method Security in Test Configuration
```java
@Configuration
@EnableMethodSecurity
class TestSecurityConfig { }
```

### 3. Test with `@WithMockUser`
```java
@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
```java
@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:
```java
@Test
void shouldRejectUnauthorizedWhenSecurityEnabled() {
  assertThatThrownBy(() -> service.deleteUser(1L))
    .isInstanceOf(AccessDeniedException.class);
}
```

## Quick Reference

| Annotation | Description | Example |
|------------|-------------|---------|
| `@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

```java
@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

```java
@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](references/basic-testing.md) for more basic patterns and [references/advanced-authorization.md](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
- **[references/setup.md](references/setup.md)** - Maven/Gradle dependencies and security configuration

### Testing Patterns
- **[references/basic-testing.md](references/basic-testing.md)** - Basic patterns for `@PreAuthorize`, `@Secured`, MockMvc testing, and parameterized tests

### Advanced Topics
- **[references/advanced-authorization.md](references/advanced-authorization.md)** - Expression-based authorization, custom permission evaluators, SpEL expressions

### Complete Examples
- **[references/complete-examples.md](references/complete-examples.md)** - Before/after examples showing transition from manual to declarative security

© giuseppe-trisciuoglio · MIT · giuseppe-trisciuoglio/developer-kit

Similar skills

All skills for Testing and QA →

From the same set96

langchain4j-rag-implementation-patterns A coding-agent skill that scaffolds LangChain4j RAG pipelines: document ingestion, embedding stores, vector search and knowledge-base answers. CX Codex +1 3,700installs nx-monorepo A skill that walks the agent through Nx: workspace setup, generators, affected commands, Module Federation and CI. CX Codex +1 3,700installs langchain4j-spring-boot-integration Configures LangChain4j in Spring Boot applications, setting up declarative @AiService interfaces, chat memory, and RAG pipelines with Spring Data. CX Codex +1 3,700installs langchain4j-tool-function-calling-patterns A skill that shows your agent how to expose Java methods with @Tool, register them in AiServices and handle tool execution errors. CX Codex +1 3,700installs spring-data-neo4j Provides integration patterns for Spring Data Neo4j in Spring Boot projects. Helps set up node mappings, Cypher queries, and repository slice tests. CX Codex +1 3,800installs sonarqube-mcp Checks quality gates, finds SonarQube and SonarCloud issues, and explains rules before code is pushed. Use it when an agent needs project or pull request quality feedback. CX Codex +1 3,600installs better-auth A skill with ready-made Better Auth patterns: NestJS backend, Drizzle ORM and PostgreSQL, social login, plus plugins like 2FA and passkey. CX Codex +1 3,600installs aws-lambda-typescript-integration Provides AWS Lambda integration patterns for TypeScript, comparing NestJS and raw handlers with cold start optimization guidelines. CX Codex +1 3,600installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor