Spring Security
Method-Level Security — @PreAuthorize, @PostAuthorize
Enforcing role clearances and domain permissions check hooks directly on Java methods.
Introduction
Spring Security allows enforcing authorization constraints directly on Java methods. Rather than checking clearances solely at URL request paths, you can declare security constraints at the service or data layers using annotations.
Why It Matters
Url-based authorization (e.g., requestMatchers("/admin/**").hasRole("ADMIN")) is clean but susceptible to route bypasses. If developers expose a new route pattern (like /internal/admin/config) that escapes path filter matchers, the API is exposed. Method security acts as a robust secondary security boundary, blocking access regardless of how the method is invoked.
Real-World Analogy
Think of method-level security like bank vault clearance checks. Checking credentials at the entrance doors (URL request filters) lets you enter the lobby. However, opening a safe deposit box (executing a service method) requires matching owner verification, irrespective of how you got inside the lobby.
Detailed Mechanics
1. Enabling Method Security
Method security is disabled by default. To enable it, you must annotate a configuration bean with @EnableMethodSecurity. Spring then uses AOP proxies to intercept annotated methods at runtime.
2. PreAuthorize vs PostAuthorize
- @PreAuthorize: Evaluates a SpEL expression *before* entering the method. If the expression evaluates to false, Spring immediately blocks execution and throws an
AccessDeniedException. Useful for role checks or checking input arguments. - @PostAuthorize: Evaluates a SpEL expression *after* the method finishes execution. The return object is accessible using the special variable
returnObject. If the expression is false, Spring throwsAccessDeniedException, preventing the caller from seeing the returned data. Useful for checking resource ownership.
3. SpEL Context Integration
Method annotations support **Spring Expression Language (SpEL)**. You can reference the principal user details (e.g. principal.username), client roles, or check method arguments directly by using parameter names prefixed with a hash (e.g. #order).
Practical Example
Below is a service class illustrating method-level security for role checks and owner constraints:
Quick Quiz
Q1: What is the main difference in behavior between @PreAuthorize and @PostAuthorize?
A) @PreAuthorize is only for administrators, while @PostAuthorize is for normal users.
B) @PreAuthorize runs before executing the method; @PostAuthorize executes the method first, then validates permissions on the returned object before returning it.
C) @PostAuthorize updates database values automatically.
D) @PreAuthorize does not support SpEL expressions.
Answer: B — @PostAuthorize lets you fetch data first, and then evaluate rules on the resulting object using returnObject properties.
Q2: How do you access a method parameter inside a @PreAuthorize SpEL expression?
A) Using the paramPrefix keyword
B) By referencing the argument name directly without any prefix
C) Using a hash character prefix (#) followed by the parameter name
D) Parameter referencing is not supported in method security
Answer: C — SpEL references method parameters using the hash symbol prefix (e.g. #accountId).
Scenario-Based Challenge
Production Scenario:
A developer configures method-level security on a service class. However, they complain that when a non-authenticated user calls the method, they receive a NullPointerException instead of the expected AccessDeniedException. What is the most likely cause?
This happens when the SpEL expression attempts to read properties from an un-authenticated principal (e.g., evaluating principal.username when principal is null).
To prevent this:
- Use safety check operators:
principal?.usernameor check if the user is authenticated first:isAuthenticated() and #owner == principal.username. - Ensure the security context filter chain runs before the service invocation so that standard authentication validation triggers first.
Interview Questions
1. Conceptual: Can method-level security be applied to private methods?
No. Spring Security relies on AOP proxies to intercept calls. AOP proxies can only intercept public method invocations. Annotating private or protected methods with method security annotations has no effect.
2. Concept: What is the difference between @PreAuthorize and @Secured?
@Secured is a legacy annotation that only supports simple role string mappings (e.g., @Secured("ROLE_ADMIN")). @PreAuthorize is modern and supports complete SpEL expression evaluations, allowing advanced conditional queries and parameter checks.
Production Considerations
Avoid writing overly complex logic directly inside SpEL strings in Java annotations. Complex logic is hard to test and maintain. Instead, write a dedicated authorization bean (e.g. @PreAuthorize("@mySecurityBean.hasAccess(#docId)")) and delegate the evaluation to standard, unit-testable Java code.