Description
Describe the bug
I don't know the exact spring security patch version where this behavior changed, but in 6.2.4 I had a setup like this:
@RestController
@PreAuthorize("hasAdminRole()") // expression doesn't matter
public class MyRestController {
@ResponseBody
@GetMapping("/status")
public String someEndpoint(){
return "foo";
}
@ExceptionHandler
public ResponseEntity<String> handleAccessDeniedException(ServletRequets request, AccessDeniedException e){
// .... build some nice string...
}
}
In Spring Security 6.2.4, if an request comes in that does not match the @PreAuthorize
condition, then Spring Security creates an AccessDeniedException
, and hands it over to the handleAccessDeniedException(...)
method. Everything was fine.
In Spring Security 6.3.1 (and maybe earlier as well, not sure, this is the version I'm currently upgrading to), something else happens:
- The REST call comes in
- The
@PreAuthorize
expression is evaluated and fails - An
AccessDeniedException
is created - Spring Security attempts to call
handleAccessDeniedException
... - ... but gets intercepted by the method security again
- ... which again throws an
AccessDeniedException
- ... which causes the original handler invocation to fail, logging the second
AccessDeniedException
as a warning with"Failure in @ExceptionHandler handleAccessDeniedException"
Overall, this results in a HTTP 403 for the client, no matter what the handleAccessDeniedException
method would have done, because it never gets called.
The workaround is to move all @ExceptionHandler
methods to an external @ControllerAdvice
class, as those are not subject to the method security imposed by the class-level @PreAuthorize
annotation. In Spring Security 6.2.4 this worked out of the box.
I think this is a weird breaking change and a potential pitfall for developers. Nobody would think that spring would intercept itself, applying method security on an exception handler method.