Spring Security: Method-Level Security


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
#parameterName
syntax)Common expressions include:
hasRole('ROLE_NAME')
orhasAuthority('AUTHORITY')
hasAnyRole('ROLE1', 'ROLE2')
principal
,authentication
objectsCustom 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
returnObject
Useful 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@PreFilter
can 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:AccessDeniedException
when authorization failsAuthenticationCredentialsNotFoundException
when no authentication exists
Migration from @EnableGlobalMethodSecurity
If you're upgrading from an older Spring Security version:
Replace
@EnableGlobalMethodSecurity
with@EnableMethodSecurity
The new version enables
prePostEnabled
by defaultjsr250Enabled
andsecuredEnabled
are 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 :)
Subscribe to my newsletter
Read articles from Phenny Mwaisaka directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by