Backend
7/21/2026
4 min read

Mastering Spring Security 7 with Spring Boot 4 and Java 21 – Part 6: Method-Level Security with @PreAuthorize, @PostAuthorize, and @Secured

Mastering Spring Security 7 with Spring Boot 4 and Java 21 – Part 6: Method-Level Security with @PreAuthorize, @PostAuthorize, and @Secured

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.

Enjoyed this article?

Subscribe to our newsletter for more backend engineering insights and tutorials.