Why Do We Need @PreAuthorize When We Already Have SecurityFilterChain in Spring Security?
One of the most common Spring Security interview questions is: "If we can secure endpoints using SecurityFilterChain, then why do we need @PreAuthorize?" At first, it seems like…
One of the most common Spring Security interview questions is:
“If we can secure endpoints using SecurityFilterChain, then why do we need @PreAuthorize?”
At first, it seems like both solve the same problem. After all, SecurityFilterChain already authenticates users and authorizes them based on roles.
So why introduce another authorization mechanism?
The answer lies in understanding the difference between URL-level security and business-level security.
Let’s understand this with real-world examples.
Understanding SecurityFilterChain
Suppose we have an e-commerce application.
There are two types of users:
- ADMIN
- CUSTOMER
We configure Spring Security as follows:
@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/orders/**").hasAnyRole("ADMIN", "CUSTOMER")
.anyRequest().authenticated());
return http.build();
}
Now every request passes through Spring Security.
If a user tries to access
/admin/**
only ADMIN is allowed.
If a user accesses
/orders/**
both ADMIN and CUSTOMER are allowed.
This is called URL-level authorization.
Spring Security simply checks:
- Is the user authenticated?
- Does the user have the required role?
If yes, the request proceeds to the controller.
For many applications, this is completely sufficient.
Does This Mean We Never Need @PreAuthorize?
Not necessarily.
If your authorization depends only on URLs and roles, then SecurityFilterChain is enough.
For example,
/admin/**
→ ADMIN
/customer/**
→ CUSTOMER
No additional authorization is required.
This is why many small applications never use @PreAuthorize.
Then Why Was @PreAuthorize Introduced?
The problem starts when authorization depends on business rules, not just the URL.
Let’s understand this with an e-commerce example.
E-commerce Example
Suppose we have an API to cancel an order.
DELETE /orders/{orderId}
Both ADMIN and CUSTOMER are allowed to access this endpoint.
So our SecurityFilterChain looks like this:
.requestMatchers("/orders/**")
.hasAnyRole("ADMIN", "CUSTOMER")
Everything looks correct.
Now imagine the following data.
| Order ID | Owner |
|---|---|
| 1001 | Alice |
| 1002 | Bob |
| 1003 | Charlie |
Bob logs into the application.
The frontend shows only Bob’s orders.
There is no Cancel button for Alice’s order.
So many developers think,
“Since the UI only shows Bob’s orders, there is no problem.”
Unfortunately, this assumption is wrong.
The Frontend Is Not a Security Boundary
A browser is only one client.
A user can completely ignore your frontend.
For example, Bob can open Postman and send
DELETE /orders/1001
or use curl
curl -X DELETE \
http://localhost:8080/orders/1001
with his valid JWT token.
He never used your frontend.
He directly called your API.
What Does SecurityFilterChain Check?
Spring Security sees:
URL = /orders/1001
Role = CUSTOMER
It checks
Can CUSTOMER access /orders/** ?
Answer:
Yes
So the request reaches the controller.
Notice something important.
Spring Security does not know
- Who owns Order 1001
- Whether Bob owns this order
- Whether Bob is allowed to cancel it
It only knows
Role = CUSTOMER
This is where URL-level authorization reaches its limit.
Business Rules Cannot Be Expressed Using URLs
Our actual business rule is
- ADMIN can cancel any order.
- CUSTOMER can cancel only their own order.
This rule has nothing to do with URLs.
Instead, it depends on business data stored in the database.
SecurityFilterChain cannot query the database and compare ownership.
This is where @PreAuthorize becomes useful.
Using @PreAuthorize
@Service
public class OrderService {
@PreAuthorize(
"hasRole('ADMIN') || @orderSecurity.isOwner(#orderId)"
)
public void cancelOrder(Long orderId) {
// Cancel order
}
}
Now Spring performs another check before executing the method.
If the user is ADMIN,
access is immediately granted.
Otherwise,
Spring calls
@Component
public class OrderSecurity {
@Autowired
private OrderRepository repository;
public boolean isOwner(Long orderId) {
String loggedInUser =
SecurityContextHolder.getContext()
.getAuthentication()
.getName();
Order order =
repository.findById(orderId)
.orElseThrow();
return order.getCustomerUsername()
.equals(loggedInUser);
}
}
Now authorization depends on actual business data.
What Happens Now?
Scenario 1
Alice logs in.
Calls
DELETE /orders/1001
Database says
Owner = Alice
Logged-in user
Alice
Result
Allowed
Scenario 2
Bob logs in.
Calls
DELETE /orders/1001
Database says
Owner = Alice
Logged-in user
Bob
Result
AccessDeniedException
The order is never cancelled.
Scenario 3
Admin logs in.
Calls
DELETE /orders/1001
Since
hasRole("ADMIN")
returns true,
ownership is not even checked.
Admin can cancel any order.
Can’t We Implement This Using SecurityFilterChain?
This is another interview question.
The answer is No.
SecurityFilterChain works at the HTTP request level.
It mainly knows
- Request URL
- HTTP Method
- Authentication
- Authorities
It does not know
- Order owner
- Product owner
- Employee department
- Loan amount
- Business status
- Database values
Those are business concepts.
Business authorization belongs inside the service layer.
A Common Misconception
Many beginners say,
“The frontend only shows the customer’s own orders, so another customer’s order cannot be cancelled.”
This is incorrect.
The frontend is only a user interface.
Anyone can bypass it using
- Postman
- curl
- Swagger
- Mobile apps
- Custom scripts
Security should always be enforced by the backend.
A famous security principle says:
Never trust the client. Trust only the server.
So When Is SecurityFilterChain Enough?
If authorization depends only on roles and URL patterns.
Example:
/admin/**
→ ADMIN
/customer/**
→ CUSTOMER
/public/**
→ Everyone
In such cases,
there is no need for @PreAuthorize.
When Should You Use @PreAuthorize?
Use @PreAuthorize whenever authorization depends on business logic.
Examples include:
- Customer can access only their own order.
- Employee can view only employees from their department.
- Manager can approve leave only for their team.
- Loan manager can approve loans below ₹50,000.
- Product seller can update only their own products.
- Doctor can access only assigned patients.
All of these require database checks or method parameters.
These cannot be expressed using simple URL matching.
SecurityFilterChain vs @PreAuthorize
| SecurityFilterChain | @PreAuthorize |
|---|---|
| Works at URL level | Works at method level |
| Checks authentication | Checks business rules |
| Checks roles | Checks roles, ownership, permissions and custom logic |
| Best for coarse-grained authorization | Best for fine-grained authorization |
| No database lookup | Can execute custom business logic |
Interview Answer
Interviewer: Why do we need @PreAuthorize if we already have SecurityFilterChain?
Answer:
“SecurityFilterChain performs coarse-grained authorization by protecting URLs based on roles or authorities. It is ideal when access depends only on the endpoint. However, many real-world applications require fine-grained authorization based on business rules such as resource ownership, order status, department, or permissions stored in the database. SecurityFilterChain cannot evaluate these business conditions. @PreAuthorize allows us to execute custom authorization logic before the service method runs, ensuring that only authorized users can perform specific business operations.”
Final Thoughts
SecurityFilterChain and @PreAuthorize are not competitors. They solve different problems.
Use SecurityFilterChain as the first line of defense to authenticate users and protect endpoints.
Use @PreAuthorize to enforce business-specific authorization rules that cannot be represented using URL patterns alone.
In enterprise applications, both are commonly used together to build a secure and maintainable authorization model.