In the previous article, we learned how to secure REST APIs using roles and authorities. While URL-based authorization works well for controlling access to endpoints, enterprise applications often require more granular security rules.
Consider an HR Management System where both managers and administrators can access the same endpoint, but only managers should approve leave requests, or an employee should only be allowed to view their own profile. These scenarios cannot be handled effectively using only URL-based security.
This is where Method-Level Security becomes essential.
Spring Security allows developers to secure individual services and controller methods using annotations such as @PreAuthorize, @PostAuthorize, @Secured, and @RolesAllowed. These annotations enable fine-grained authorization based on roles, authorities, method parameters, and even return values.
In this article, we will learn how to implement method-level security in Spring Security 7 using Spring Boot 4 and Java 21.
What is Method-Level Security?
Method-level security protects individual Java methods rather than HTTP endpoints.
Instead of securing URLs, security rules are applied directly to business logic.
Example:
Only administrators can delete users.
Only managers can approve leave requests.
Customers can only access their own orders.
Employees can update only their own profiles.
This approach keeps authorization close to the business logic.
Why Use Method-Level Security?
Imagine an Employee Management System.
Both of these URLs may point to the same service method:
GET /employees/1
GET /employees/2
URL-based security cannot determine whether the logged-in employee is requesting their own profile.
Method-level security allows us to compare the authenticated user with the requested resource before granting access.
Enabling Method Security
Spring Security 7 uses @EnableMethodSecurity.
@Configuration
@EnableWebSecurity
@EnableMethodSecuritypublicclassSecurityConfig {
}
Once enabled, Spring Security starts processing method security annotations throughout the application.
Using @PreAuthorize
@PreAuthorize checks authorization before a method executes.
Example:
@ServicepublicclassEmployeeService {
@PreAuthorize("hasRole('ADMIN')")publicvoiddeleteEmployee(Longid) {
}
}
Only users with the ADMIN role can execute this method.
Understanding the Expression
@PreAuthorize("hasRole('ADMIN')")
Spring evaluates the expression before invoking the method.
If the condition is false:
403 Forbidden
is returned
Using Authorities
Instead of roles, permissions can also be checked.
@PreAuthorize("hasAuthority('DELETE_USERS')")publicvoiddeleteUser(Longid) {
}
Only users with the DELETE_USERS authority can execute the method.
Using Multiple Roles
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")publicList<Employee>getEmployees() {
}
Both administrators and managers are allowed.
Combining Multiple Authorities
@PreAuthorize("hasAuthority('READ_USERS') and hasAuthority('VIEW_REPORTS')")
The authenticated user must possess both permissions.
Using OR Conditions
@PreAuthorize("hasRole('ADMIN') or hasRole('MANAGER')")
Either role is sufficient.
Securing Methods with Parameters
One of the biggest advantages of @PreAuthorize is access to method parameters.
Example:
@PreAuthorize("#id == authentication.principal.id")publicEmployeegetEmployee(Longid) {
}
Now users can only access their own records.
This is impossible with traditional URL-based authorization alone.
Real-World Example
Suppose a banking application exposes:
@GetMapping("/accounts/{id}")
Without method security:
A customer could request another customer's account simply by changing the ID.
With:
@PreAuthorize("#id == authentication.principal.id")
Only the owner can access the account.
Using @PostAuthorize
@PostAuthorize executes after the method returns.
Example:
@PostAuthorize("returnObject.owner == authentication.name")publicAccountgetAccount(Longid) {
}
Spring first executes the method.
Then it checks whether the returned account belongs to the logged-in user.
If not:
403 Forbidden
is returned.
When Should You Use @PostAuthorize?
Useful when authorization depends on the returned object.
Examples:
Banking
Healthcare
Insurance
HR systems
Using @Secured
@Secured is a simpler annotation.
@Secured("ROLE_ADMIN")publicvoiddeleteUser() {
}
It only supports roles.
It does not support SpEL expressions.
Using @RolesAllowed
Spring Security also supports JSR-250 annotations.
@RolesAllowed("ADMIN")publicvoidcreateUser() {
}
This is useful when working with Jakarta EE standards.
@PreAuthorize vs @Secured vs @RolesAllowed
Annotation | Roles | Authorities | SpEL | Recommended |
|---|---|---|---|---|
@PreAuthorize | ✅ | ✅ | ✅ | ✅ |
@PostAuthorize | ✅ | ✅ | ✅ | ✅ |
@Secured | ✅ | ❌ | ❌ | Limited |
@RolesAllowed | ✅ | ❌ | ❌ | Standard Java |
For most modern Spring Boot applications, @PreAuthorize is the preferred choice.
Real-World Example
Consider an Order Service.
@ServicepublicclassOrderService {
@PreAuthorize("hasAuthority('CREATE_ORDER')")publicvoidcreateOrder() {
}
@PreAuthorize("hasAuthority('UPDATE_ORDER')")publicvoidupdateOrder() {
}
@PreAuthorize("hasAuthority('DELETE_ORDER')")publicvoiddeleteOrder() {
}
}
Each business operation has its own permission.
This approach provides much greater flexibility than using roles alone.
Common Mistakes
Securing Only Controllers
Business logic should also be protected.
Attackers may bypass controllers through internal calls.
Using Roles Everywhere
Prefer authorities for enterprise applications.
Roles should group permissions.
Permissions should secure business operations.
Hardcoding Usernames
Avoid expressions like:
@PreAuthorize("authentication.name == 'admin'")
Instead, rely on roles and authorities.
Ignoring Service Layer Security
Even if controllers are protected, services should also enforce authorization rules.
Production-Level Best Practices
Enable Method Security
Always add:
@EnableMethodSecurity
Use @PreAuthorize
It is the most flexible and feature-rich annotation.
Keep Authorization Close to Business Logic
Place authorization annotations on service methods instead of controllers whenever possible.
Use Authorities for Sensitive Operations
Examples:
CREATE_PAYMENT
APPROVE_LOAN
DELETE_USER
VIEW_FINANCIAL_REPORT
Authorities provide precise control.
Audit Sensitive Operations
Log:
User ID
Method name
Timestamp
Decision (Allowed or Denied)
This is important for compliance and security investigations.
Summary
In this article, we learned:
What is method-level security?
Enabling
@EnableMethodSecurity.Using
@PreAuthorize.Using
@PostAuthorize.Using
@Secured.Using
@RolesAllowed.Securing methods with roles, authorities, and method parameters.
Production-level best practices.
In Part 7, we will build a complete JWT Authentication and Authorization system using Spring Security 7. We will implement stateless authentication, generate and validate JWT tokens, create login and registration APIs, build a custom JWT filter, and secure REST APIs using bearer tokens—the authentication approach used in most modern production applications.



