
Introduction
Spring Security provides robust mechanisms for securing applications at both the web request level and the method level. While request-level security handles URL-based access control, method-level security offers finer-grained control over individual methods in your application. This article explores the key annotations and techniques for implementing method-level security in Spring applications.
Enabling Method-Level Security
To use method-level security in your Spring application, you must first enable it in your security configuration:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
// Other security configurations
}
The @EnableMethodSecurity annotation activates Spring's method-level security features. This annotation replaces the older @EnableGlobalMethodSecurity (deprecated as of Spring Security 6.0).
Method Security Annotations
1. @Secured Annotation
The @Secured annotation is the simplest way to secure methods based on user roles.
@Secured("ROLE_ADMIN")
public void deleteUser(int userId) {
// Method logic here
}
Key points about @Secured:
Only checks role-based authorization
Roles must be prefixed with "ROLE_"
Supports multiple roles (e.g.,
@Secured({"ROLE_ADMIN", "ROLE_SUPERADMIN"}))Does not support SpEL expressions
2. @PreAuthorize Annotation
The @PreAuthorize annotation offers more flexibility by using Spring Expression Language (SpEL) to define access conditions.
@PreAuthorize("hasRole('ROLE_ADMIN') or (hasRole('ROLE_USER') and #userId == authentication.principal.id)")
public void updateUser(int userId) {
// Method logic here
}
Key features of @PreAuthorize:
Evaluates before method execution
Supports complex expressions using SpEL
Can access method parameters (using
#parameterNamesyntax)Common expressions include:
hasRole('ROLE_NAME')orhasAuthority('AUTHORITY')hasAnyRole('ROLE1', 'ROLE2')principal,authenticationobjectsCustom security expressions
3. @PostAuthorize Annotation
The @PostAuthorize annotation checks authorization after method execution, allowing you to filter return values.
@PostAuthorize("returnObject.ownerId == authentication.principal.id")
public User getUserDetails(int userId) {
return userService.findById(userId);
}
Key aspects of @PostAuthorize:
Evaluates after method execution
Can access the return value via
returnObjectUseful for filtering sensitive data
Be aware it executes the method first, then checks authorization
4. @PreFilter and @PostFilter Annotations
Two additional annotations provide collection filtering:
@PreFilter filters collection arguments before method execution:
@PreFilter("filterObject.owner == authentication.name")
public void updateAccounts(List<Account> accounts) {
// Method logic
}
@PostFilter filters collection return values:
@PostFilter("filterObject.owner == authentication.name")
public List<Account> getAllAccounts() {
// Method logic
}
Best Practices and Additional Considerations
Performance Implications:
@PostAuthorize,@PostFilter, and@PreFiltercan impact performance with large collectionsConsider filtering at the database level for large datasets
Method Security with Spring Data Repositories:
@Repository public interface UserRepository extends JpaRepository<User, Long> { @PreAuthorize("#entity.owner == authentication.name") <S extends User> S save(S entity); }Custom Security Expressions:
You can create custom security expressions by extendingSecurityExpressionRoot:public class CustomSecurityExpressionRoot extends SecurityExpressionRoot { public boolean isMember(Long organizationId) { // Custom logic } }Testing Method Security:
Always test secured methods with different user roles:
@Test @WithMockUser(roles = "ADMIN") public void testAdminAccess() { // Test admin-accessible methods }Exception Handling:
Method security throws:AccessDeniedExceptionwhen authorization failsAuthenticationCredentialsNotFoundExceptionwhen no authentication exists
Migration from @EnableGlobalMethodSecurity
If you're upgrading from an older Spring Security version:
Replace
@EnableGlobalMethodSecuritywith@EnableMethodSecurityThe new version enables
prePostEnabledby defaultjsr250EnabledandsecuredEnabledare disabled by default
My Takeaways
By combining these annotations with Spring's expression language, you can create sophisticated security rules that match your business requirements while keeping your code clean and maintainable.
Remember to:
Choose the appropriate annotation for your use case
Consider performance implications for post-execution filtering
Thoroughly test all security constraints
Document your security requirements alongside the code
Method-level security complements request-level security to create a comprehensive security strategy for your Spring applications.
If you loved this article, do consider following me :)



