{"record":{"id":"d74b6f4ce73a19ce","repo":"spring-projects/spring-security","slug":"access-is-denied","errorCode":null,"errorMessage":"Access is denied","messagePattern":"Access is denied","errorType":"exception","errorClass":"AccessDeniedException","httpStatus":403,"severity":"error","filePath":"access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPostInvocationAdvice.java","lineNumber":72,"sourceCode":"\tpublic Object after(Authentication authentication, MethodInvocation mi, PostInvocationAttribute postAttr,\n\t\t\tObject returnedObject) throws AccessDeniedException {\n\t\tPostInvocationExpressionAttribute pia = (PostInvocationExpressionAttribute) postAttr;\n\t\tEvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);\n\t\tExpression postFilter = pia.getFilterExpression();\n\t\tExpression postAuthorize = pia.getAuthorizeExpression();\n\t\tif (postFilter != null) {\n\t\t\tthis.logger.debug(LogMessage.format(\"Applying PostFilter expression %s\", postFilter));\n\t\t\tif (returnedObject != null) {\n\t\t\t\treturnedObject = this.expressionHandler.filter(returnedObject, postFilter, ctx);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis.logger.debug(\"Return object is null, filtering will be skipped\");\n\t\t\t}\n\t\t}\n\t\tthis.expressionHandler.setReturnObject(returnedObject, ctx);\n\t\tif (postAuthorize != null && !ExpressionUtils.evaluateAsBoolean(postAuthorize, ctx)) {\n\t\t\tthis.logger.debug(\"PostAuthorize expression rejected access\");\n\t\t\tthrow new AccessDeniedException(\"Access is denied\");\n\t\t}\n\t\treturn returnedObject;\n\t}\n\n}\n","sourceCodeStart":54,"sourceCodeEnd":78,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPostInvocationAdvice.java#L54-L78","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n@PostAuthorize(\"returnObject.owner == authentication.name\")\npublic Account getAccount(Long id) { ... }\n\n// after\n@PostAuthorize(\"returnObject == null || returnObject.owner == authentication.name\")\npublic Account getAccount(Long id) { ... }","handlingStrategy":"try-catch","validationCode":"// before calling the secured method\nObject returned = null; // result only known post-invocation; validate post-hoc\nif (returned != null && !Objects.equals(getOwner(returned), currentUsername)) {\n    throw new AccessDeniedException(\"Predicted post-authorize failure\");\n}","typeGuard":"boolean canView(Object returnObject, Authentication auth) {\n    return returnObject == null\n        || (returnObject instanceof Account a && auth.getName().equals(a.getOwner()));\n}","tryCatchPattern":"try {\n    Object result = securedService.method();\n} catch (AccessDeniedException e) {\n    log.warn(\"PostAuthorize rejected return object\", e);\n    throw new ResponseStatusException(HttpStatus.FORBIDDEN, \"Access denied\");\n}","preventionTips":["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"],"tags":["spring-security","authorization","post-authorize","spel","access-denied"],"backgroundTag":"permission-denied","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}