CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

90

Quality

90%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

This version of the tile failed moderation
Moderation pipeline encountered an internal error
Overview
Quality
Evals
Security
Files

complete-examples.mdplugins/developer-kit-java/skills/unit-test-security-authorization/references/

Complete Examples - Before and After

Example 1: Adding Security Tests

Input: Service Without Security Testing

@Service
public class AdminService {
    public void deleteUser(Long userId) {
        // Delete logic without security check
        repository.deleteById(userId);
    }
}

Output: Service With Security Test Coverage

@Service
public class AdminService {
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long userId) {
        // Delete logic with security check
        repository.deleteById(userId);
    }
}

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

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

Example 2: Replacing Manual Security Checks

Input: Manual Security Check (Anti-Pattern)

@Service
public class AdminService {
    private final UserRepository userRepository;

    public void deleteUser(Long userId, User currentUser) {
        // Manual security check in business logic
        if (currentUser.hasRole("ADMIN")) {
            repository.deleteById(userId);
        } else {
            throw new AccessDeniedException("Not authorized");
        }
    }
}

Output: Declarative Security with Testing

@Service
public class AdminService {
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long userId) {
        // Business logic only, security is declarative
        repository.deleteById(userId);
    }
}

// Test verifies security enforcement
@Test
@WithMockUser(roles = "ADMIN")
void shouldExecuteDelete() {
    service.deleteUser(1L);
    verify(repository).deleteById(1L);
}

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

    verify(repository, never()).deleteById(anyLong());
}

Example 3: Controller Security Testing

Input: Insecure Controller

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(service.findById(id));
    }

    @DeleteMapping("/users/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        service.deleteUser(id);
        return ResponseEntity.ok().build();
    }
}

Output: Secure Controller with Tests

@RestController
@RequestMapping("/api/admin")
public class AdminController {

    @GetMapping("/users/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(service.findById(id));
    }

    @DeleteMapping("/users/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        service.deleteUser(id);
        return ResponseEntity.ok().build();
    }
}

// Tests
@SpringBootTest
@AutoConfigureMockMvc
class AdminControllerSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(roles = "ADMIN")
    void shouldAllowAdminToGetUser() throws Exception {
        mockMvc.perform(get("/api/admin/users/1"))
            .andExpect(status().isOk());
    }

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

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

Example 4: Custom Permission Evaluator

Input: Inline Permission Check

@Service
public class DocumentService {
    private final DocumentRepository repository;

    public Document getDocument(Long docId, User currentUser) {
        Document doc = repository.findById(docId)
            .orElseThrow(() -> new NotFoundException());

        // Inline permission check
        if (!doc.getOwner().equals(currentUser.getUsername()) &&
            !currentUser.hasRole("ADMIN")) {
            throw new AccessDeniedException("Access denied");
        }

        return doc;
    }
}

Output: Declarative Security with Custom Evaluator

@Service
public class DocumentService {

    @PreAuthorize("hasPermission(#docId, 'Document', 'READ')")
    public Document getDocument(Long docId) {
        return repository.findById(docId)
            .orElseThrow(() -> new NotFoundException());
    }
}

// Custom Permission Evaluator
@Component
public class DocumentPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication,
                               Serializable targetId,
                               String targetType,
                               Object permission) {
        // Permission logic extracted and reusable
        Document doc = repository.findById(targetId).orElse(null);
        if (doc == null) return false;

        return doc.getOwner().equals(authentication.getName()) ||
               hasRole(authentication, "ADMIN");
    }
}

// Test
@SpringBootTest
class DocumentServiceSecurityTest {

    @Autowired
    private DocumentService service;

    @Test
    @WithMockUser(username = "alice")
    void shouldAllowOwnerToReadDocument() {
        Document doc = service.getDocument(1L);
        assertThat(doc.getOwner()).isEqualTo("alice");
    }

    @Test
    @WithMockUser(username = "alice")
    void shouldDenyNonOwnerFromReadingDocument() {
        assertThatThrownBy(() -> service.getDocument(2L))
            .isInstanceOf(AccessDeniedException.class);
    }

    @Test
    @WithMockUser(roles = "ADMIN")
    void shouldAllowAdminToReadAnyDocument() {
        Document doc = service.getDocument(2L);
        assertThat(doc).isNotNull();
    }
}

Example 5: Complex Expression-Based Security

Input: Multiple Manual Checks

@Service
public class ProfileService {

    public UserProfile updateProfile(Long userId, ProfileUpdate update, User currentUser) {
        // Multiple manual checks
        if (!currentUser.getId().equals(userId) &&
            !currentUser.hasRole("ADMIN") &&
            !currentUser.hasRole("MODERATOR")) {
            throw new AccessDeniedException("Access denied");
        }

        if (update.isPublic() && !currentUser.isVerified()) {
            throw new AccessDeniedException("Verified users only");
        }

        return repository.update(userId, update);
    }
}

Output: Declarative Expression-Based Security

@Service
public class ProfileService {

    @PreAuthorize("#userId == authentication.principal.id or " +
                  "hasAnyRole('ADMIN', 'MODERATOR')")
    public UserProfile updateProfile(Long userId, ProfileUpdate update) {
        return repository.update(userId, update);
    }

    @PreAuthorize("isVerified() and hasRole('USER')")
    public void makePublic(Long userId) {
        repository.setPublic(userId, true);
    }
}

// Test
@SpringBootTest
class ProfileServiceSecurityTest {

    @Autowired
    private ProfileService service;

    @Test
    @WithMockUser(username = "alice", id = "1")
    void shouldAllowUserToUpdateOwnProfile() {
        ProfileUpdate update = new ProfileUpdate("Alice Updated");
        assertThatCode(() -> service.updateProfile(1L, update))
            .doesNotThrowAnyException();
    }

    @Test
    @WithMockUser(username = "alice", id = "1")
    void shouldDenyUserFromUpdatingOtherProfile() {
        ProfileUpdate update = new ProfileUpdate("Hacked");
        assertThatThrownBy(() -> service.updateProfile(2L, update))
            .isInstanceOf(AccessDeniedException.class);
    }

    @Test
    @WithMockUser(roles = "ADMIN")
    void shouldAllowAdminToUpdateAnyProfile() {
        ProfileUpdate update = new ProfileUpdate("Admin Update");
        assertThatCode(() -> service.updateProfile(2L, update))
            .doesNotThrowAnyException();
    }

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

Key Takeaways

  1. Move from imperative to declarative security - Use annotations instead of manual checks
  2. Separate security logic from business logic - Keep code focused on business requirements
  3. Test both positive and negative cases - Verify both access granted and denied scenarios
  4. Use appropriate test annotations - @WithMockUser for most cases, custom setup for complex scenarios
  5. Test with MockMvc for controllers - Verifies security filters are properly configured
  6. Create reusable permission evaluators - Extract complex permission logic into dedicated components

plugins

developer-kit-java

skills

README.md

CHANGELOG.md

context7.json

CONTRIBUTING.md

README_CN.md

README_ES.md

README_IT.md

README.md

tessl.json

tile.json