The labs on this site need JavaScript to run. The same content is written out below.
Learn › Security › Authorization
When does an authorization check on a method run, and when is it skipped?
Method security is a way to write "who may run this method" on a method instead of on a URL. @PreAuthorize evaluates its expression before the method runs, and @PostAuthorize evaluates its expression after the method returns a value. If the expression is false, the call is denied. But these annotations sometimes do nothing just because you added them.
Checked against real execution · All 36 combinations were run on Spring Security 7.1.1, and whether the check ran, whether the body ran, and the value or exception received were checked against the results
@Configuration
// there is no @EnableMethodSecurity
class SecurityConfig { }
@Service
class OrderService {
@PreAuthorize("hasRole('ADMIN')") // check before the method runs
public String deleteAll() {
orders.clear();
return "Deleted all";
}
}
@RestController
class OrderController {
private final OrderService orderService; // the bean that Spring injected
OrderController(OrderService orderService) { this.orderService = orderService; }
@GetMapping("/try")
Object call() { // the user logged in now: kim (ROLE_USER)
return orderService.deleteAll();
}
}
All 36 combinations were run and checked on the PC used to write this lab with Spring Security 7.1.1 (Spring Framework 7.0.9, JDK 21), using code of the same structure as on the screen. The combinations were checked without a web server, by calling the methods of the service bean directly from a Spring context, and the logged-in user was put directly into the SecurityContext. Whether the authorization check ran was counted with an AuthorizationEventPublisher bean, which is called each time a check is made, and whether the method body ran, the value or the kind of exception the caller received, and whether the bean was a proxy were checked together. That AuthorizationDeniedException, which is thrown on denial, is a kind of AccessDeniedException was also checked by execution. That this exception becomes a 403 response in a web request was checked separately by starting a Spring Boot 4.1.1 server on 127.0.0.1. A request as kim, who has USER, to the URL where the controller calls deleteAll() returned 403 and the body did not run, and a request as park, who has ADMIN, returned 200. To use the name of an argument such as #owner in an expression, the -parameters option is needed at compile time. When the code was compiled without this option and run, #owner was empty and even the call by kim, the owner, was denied (the documentation says that the build tools of Spring Boot turn this option on by default, but that part was not verified). When a method with a check is called with no login information at all, AuthenticationCredentialsNotFoundException came out, not AuthorizationDeniedException. The body code on the screen (orders.clear() and so on) is abbreviated to show the meaning, and in the actual run, the number of times the body ran was counted. Spring Boot did not turn this setting on by itself. On the same Spring Boot server, when it was run with only @EnableMethodSecurity removed, even the request from kim with USER got 200 and the body ran. That the check is skipped on a call inside the same class is a general limitation of the proxy approach, and it is covered in detail in the "Proxies and self-invocation" lab. @PreFilter, @PostFilter, @Secured, and calling a bean you made yourself from an expression were not covered.
Previous lab: Access control · Next lab: Three web attacks
Struck-through text was removed from the first version of the code; highlighted text was added.