How are method-security checks physically applied — how are the interceptors registered and ordered as advisors?
answer
- Each annotation → a PointcutAdvisor bean (ROLE_INFRASTRUCTURE)
- AuthorizationManagerBefore/AfterMethodInterceptor + Pre/PostFilter interceptors
- InfrastructureAdvisorAutoProxyCreator builds the proxy
- Order fixed by AuthorizationInterceptorsOrder enum
- static @Bean advisors; self-invocation bypasses
basics
~20 s@EnableMethodSecurity registers a set of AOP advisor beans (pointcut + interceptor pairs), one per annotation type. Spring proxies your secured beans; when a matching method is called, the advisor's interceptor runs an AuthorizationManager to allow or deny. Their run order is fixed by AuthorizationInterceptorsOrder.
solid answer
~40 sMethod security is pure Spring AOP. @EnableMethodSecurity imports configuration that publishes several Advisor beans — each is a PointcutAdvisor whose pointcut matches methods carrying a given annotation and whose advice is an AuthorizationManager-based MethodInterceptor: AuthorizationManagerBeforeMethodInterceptor for @PreAuthorize, AuthorizationManagerAfterMethodInterceptor for @PostAuthorize, PreFilterAuthorizationMethodInterceptor, PostFilterAuthorizationMethodInterceptor, and equivalents for @Secured and the JSR-250 annotations. These advisor beans are registered with ROLE_INFRASTRUCTURE. The auto-proxy creator then wraps matching beans in a proxy, so an external call passes through the interceptor, which invokes the AuthorizationManager and throws AccessDeniedException on a deny. Because multiple interceptors can apply to one method, their execution order is defined by the AuthorizationInterceptorsOrder enum — pre-filter, then pre-authorize, then secured, then JSR-250, the method, then post-authorize, then post-filter — implemented via each advisor's Ordered value.
code
java · 17 lines// Registering a custom advisor and placing it in the interceptor order.
@Configuration
@EnableMethodSecurity
public class CustomMethodSecurity {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor tenantAuthorizationAdvisor(TenantService svc) {
AuthorizationManager<MethodInvocation> mgr =
(auth, mi) -> new AuthorizationDecision(svc.isInTenant(auth.get()));
var interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AnnotationMatchingPointcut.forMethodAnnotation(TenantChecked.class), mgr);
// run just before the standard @PreAuthorize interceptor
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() - 1);
return interceptor;
}
}go deeper
Enough to know it's proxy-based and a check runs around the method.
Know it's Spring AOP with an interceptor per annotation and the self-invocation caveat.
Name the AuthorizationManager*MethodInterceptor advisors and that auto-proxying wires them.
Explain advisor registration (ROLE_INFRASTRUCTURE, static @Bean), the AuthorizationInterceptorsOrder ordering, and how to insert a custom interceptor at a precise slot.
## The whole mechanism is Spring AOP `@EnableMethodSecurity` doesn't invent a special container feature; it plugs into the ordinary **Spring AOP advisor + auto-proxy** infrastructure. ### 1. Advisor beans are registered The imported configuration (`MethodSecuritySelector` → `PrePostMethodSecurityConfiguration` and friends) declares, for each enabled annotation family, an **`Advisor`** bean. An `Advisor` bundles: - a **`Pointcut`** — matches methods (or classes) annotated with the target annotation, and - an **advice** — here an `AuthorizationManager`-backed `MethodInterceptor`. The concrete interceptor/advisor types include: - **`AuthorizationManagerBeforeMethodInterceptor`** — used for `@PreAuthorize` (and the `@Secured` / JSR-250 variants are also *before* interceptors, built via factory methods like `AuthorizationManagerBeforeMethodInterceptor.secured(...)` / `.jsr250(...)`). Runs the `AuthorizationManager` *before* the method. - **`AuthorizationManagerAfterMethodInterceptor`** — used for `@PostAuthorize`; runs *after* and can see the return value. - **`PreFilterAuthorizationMethodInterceptor`** / **`PostFilterAuthorizationMethodInterceptor`** — mutate collection args/returns. Each is published as a bean annotated `@Role(BeanDefinition.ROLE_INFRASTRUCTURE)` (so it's treated as plumbing, not app config) and typically `static @Bean` (to avoid premature bean initialization / to work before the bean factory is fully configured). ### 2. Auto-proxying wires them in Because these are `Advisor` beans, the standard **`InfrastructureAdvisorAutoProxyCreator`** (a `BeanPostProcessor`) sees them and, for every application bean whose methods match any advisor's pointcut, creates a **proxy** (JDK dynamic proxy if the bean implements interfaces, else CGLIB subclass). The proxy holds the interceptor chain. ### 3. At call time An **external** call hits the proxy → the applicable interceptor(s) run → each builds/uses its `AuthorizationManager` to produce an `AuthorizationDecision`. A *before* interceptor that denies throws `AccessDeniedException` before the target runs; an *after* interceptor can deny based on the result; filter interceptors strip disallowed elements. If granted, the call proceeds to the real method. ### 4. Ordering — `AuthorizationInterceptorsOrder` Since several interceptors may apply to the same method, order matters. Spring Security defines the enum **`AuthorizationInterceptorsOrder`** giving each advisor an `Ordered` value, roughly: `FIRST → PRE_FILTER → PRE_AUTHORIZE → SECURED → JSR250 → (method body) → POST_AUTHORIZE → POST_FILTER → LAST`. So filtering of inputs happens before the pre-authorize gate, the method runs, then post-authorize/post-filter act on the output. You can insert a custom interceptor at a chosen slot by giving it an order relative to these constants. ## Why this design matters (principal lens) - **Extensibility**: you can register your own `Advisor` / `AuthorizationManager` and slot it into the order — no subclassing of a monolithic config. - **Uniformity**: the same `AuthorizationManager` abstraction backs both request and method authorization. - **Observability/testing**: because it's plain AOP, you can reason about proxy creation, and diagnose "annotation not firing" as a proxy/self-invocation problem. ## Gotchas - **Self-invocation** bypasses the proxy → no interceptor → no check. Split beans or use `AopContext.currentProxy()` (with `exposeProxy`). - **`final`/`private` methods** can't be advised by CGLIB/JDK proxies. - **`static @Bean` requirement**: declaring these interceptor beans non-static can trigger warnings or eager-init ordering issues. - Bean must be **container-managed**; annotations on `new`-ed objects do nothing. - Changing `proxyTargetClass`/`mode` on `@EnableMethodSecurity` alters proxy strategy and thus what can be advised.
- What determines the execution order when @PreFilter, @PreAuthorize and @PostFilter all apply to one method?The AuthorizationInterceptorsOrder enum assigns each advisor an Ordered value: PRE_FILTER runs, then PRE_AUTHORIZE, then the method, then POST_AUTHORIZE, then POST_FILTER. Custom interceptors slot in by choosing an order relative to those constants.
- Why are the interceptor beans declared as static @Bean methods with ROLE_INFRASTRUCTURE?static avoids forcing the @Configuration class (and other beans) to initialize too early, preventing bean-post-processor ordering problems; ROLE_INFRASTRUCTURE marks them as framework plumbing so tools/auto-proxying treat them as infrastructure, not application beans.
saying these in an interview costs you the question
- Saying method security uses a servlet Filter rather than AOP interceptors.
- Claiming the interceptors run in annotation-declaration order rather than AuthorizationInterceptorsOrder.
- Thinking each secured method gets a bespoke proxy class unrelated to standard auto-proxying.
- Believing self-invocation still triggers the interceptor.