A post-invocation authorization check can prevent a return value from reaching the caller, but it cannot undo arbitrary side effects.
Spring @PostAuthorize: denial happens after the method has run
Check timing before using returnObject
A service loads a payroll record and @PostAuthorize checks that its owner matches the caller. The decision can inspect the returned object, but the method has already executed. If the method also sent a message, incremented a counter or called another service, denial does not rewind that work. For a write, prefer a pre-invocation rule based on authenticated tenant and arguments; use database predicates to enforce row ownership.
Keep the denied result contained
Do not log the protected object before the post-check. A repository query should include tenant and owner filters where possible, limiting both data access and accidental exposure. Method advice requires a proxy invocation. A 403 response is the HTTP expression of an authenticated caller failing authorization, but message and batch callers need their own error mapping.
Probe the side effect
Build a test method that would publish an event, then call it as a denied principal. If the event still appears, the policy is too late. Move the guard before the operation and repeat the test. The annotation sketch is appropriate only for a side-effect-free read with a return object whose owner is authoritative.
Implementation sketch
@PostAuthorize("returnObject.ownerId == authentication.name")
public PayrollView readPayroll(String payrollId) {
return payrollReadStore.load(payrollId);
}Cost and verification
A post-check may load a full record only to deny it, wasting I/O. A tenant-scoped query and pre-check usually cut this work while reducing the data-exposure surface.
Common Mistakes
- Do not use @PostAuthorize to protect a method that performs an irreversible side effect.
- Do not log the returned object before post-authorization succeeds.
- Do not assume a denied return value means the database query never ran.
Read next
Spring method security: authorization advice runs through the bean proxy, Spring Security 401 versus 403: authentication and access are separate failures, Spring JWT tenant claims: reject missing or malformed ownership before conversion, Spring outbox transaction boundaries: where atomicity ends.
