spring-projects/spring-security · error · AccessDeniedException
Access is denied
Error message
Access is denied
What it means
ExpressionBasedPostInvocationAdvice evaluates @PostAuthorize expressions after a secured method returns. When the SpEL expression evaluates to false, it throws AccessDeniedException('Access is denied'). The method's result is discarded and the caller receives an authorization failure.
Source
Thrown at access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPostInvocationAdvice.java:72
public Object after(Authentication authentication, MethodInvocation mi, PostInvocationAttribute postAttr,
Object returnedObject) throws AccessDeniedException {
PostInvocationExpressionAttribute pia = (PostInvocationExpressionAttribute) postAttr;
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);
Expression postFilter = pia.getFilterExpression();
Expression postAuthorize = pia.getAuthorizeExpression();
if (postFilter != null) {
this.logger.debug(LogMessage.format("Applying PostFilter expression %s", postFilter));
if (returnedObject != null) {
returnedObject = this.expressionHandler.filter(returnedObject, postFilter, ctx);
}
else {
this.logger.debug("Return object is null, filtering will be skipped");
}
}
this.expressionHandler.setReturnObject(returnedObject, ctx);
if (postAuthorize != null && !ExpressionUtils.evaluateAsBoolean(postAuthorize, ctx)) {
this.logger.debug("PostAuthorize expression rejected access");
throw new AccessDeniedException("Access is denied");
}
return returnedObject;
}
}
View on GitHub (pinned to 96852e8860)
Solutions
- Inspect the @PostAuthorize SpEL expression and the returned object's properties to see why it evaluates false
- Ensure returnObject is non-null or guard the expression, e.g. @PostAuthorize("returnObject == null or returnObject.owner == authentication.name")
- Verify the expression handler has access to needed beans/properties (useCustomPermissionEvaluator, RoleHierarchy)
- Temporarily log the returned object and authentication to debug the expression
- Catch AccessDeniedException at the caller/web layer and map to HTTP 403
Example fix
// before
@PostAuthorize("returnObject.owner == authentication.name")
public Account getAccount(Long id) { ... }
// after
@PostAuthorize("returnObject == null || returnObject.owner == authentication.name")
public Account getAccount(Long id) { ... } Defensive patterns
Strategy: try-catch
Validate before calling
// before calling the secured method
Object returned = null; // result only known post-invocation; validate post-hoc
if (returned != null && !Objects.equals(getOwner(returned), currentUsername)) {
throw new AccessDeniedException("Predicted post-authorize failure");
} Type guard
boolean canView(Object returnObject, Authentication auth) {
return returnObject == null
|| (returnObject instanceof Account a && auth.getName().equals(a.getOwner()));
} Try / catch
try {
Object result = securedService.method();
} catch (AccessDeniedException e) {
log.warn("PostAuthorize rejected return object", e);
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied");
} Prevention
- Guard SpEL expressions against null returnObject
- Unit test @PostAuthorize expressions with representative return objects
- Keep return types and their owner properties consistent
- Map AccessDeniedException to HTTP 403 via ExceptionHandling
When it happens
Trigger: A @PostAuthorize("returnObject.owner == authentication.name") (or similar) expression evaluates to false on the returned object; thrown from afterInvocation after the method body already executed.
Common situations: Returning an entity owned by another user; forgetting that returnObject may be null (filtering skipped, null returned if expression not evaluated) or accessing properties of the wrong return type; SpEL typo so expression compares unequal unexpectedly.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/d74b6f4ce73a19ce.
Report an issue: GitHub.