How does Spring Security 6 process @Secured and @RolesAllowed under the hood, and what changed from @EnableGlobalMethodSecurity?
answer
- SecuredAuthorizationManager / Jsr250AuthorizationManager
- AuthorizationManager replaced voters/AccessDecisionManager
- prePostEnabled default true in SS6
- proxy-based, self-invocation bypasses
- Jsr250 uses GrantedAuthorityDefaults prefix
basics
~10 sIn Spring Security 6, @EnableMethodSecurity builds AOP interceptors backed by AuthorizationManager beans — SecuredAuthorizationManager for @Secured and Jsr250AuthorizationManager for @RolesAllowed. This replaced the old @EnableGlobalMethodSecurity model that used AccessDecisionManager and voters.
solid answer
~40 s@EnableMethodSecurity registers method interceptors (Spring AOP) that delegate to AuthorizationManager implementations: SecuredAuthorizationManager handles @Secured, Jsr250AuthorizationManager handles @RolesAllowed (applying the ROLE_ prefix from GrantedAuthorityDefaults), and PreAuthorizeAuthorizationManager handles @PreAuthorize. Each returns an AuthorizationDecision; a deny throws AccessDeniedException before the method runs. This is the Spring Security 6 model that replaced @EnableGlobalMethodSecurity, which used an AccessDecisionManager with AccessDecisionVoters (e.g. RoleVoter, Jsr250Voter) and consensus/affirmative strategies. The new model is simpler (no voter aggregation), uses AuthorizationManager everywhere, and defaults prePostEnabled to true (the old annotation defaulted it to false). Enforcement is proxy-based, so self-invocation bypasses checks and, by default, only public methods on Spring beans are advised. You can also customize ordering or wrap managers via AuthorizationManagerBeforeMethodInterceptor.
code
java · 12 lines@Configuration
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {
// @RolesAllowed / hasRole prefix comes from here (not @Secured)
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("ROLE_");
}
}
// @Secured -> SecuredAuthorizationManager (literal authority match)
// @RolesAllowed -> Jsr250AuthorizationManager (adds ROLE_ prefix)
// @PreAuthorize -> PreAuthorizeAuthorizationManager (SpEL)go deeper
Know they run via a proxy interceptor before the method executes.
Name @EnableMethodSecurity and that self-invocation bypasses checks.
Identify the AuthorizationManager implementations and the JSR-250 prefix source.
Explain the voter->AuthorizationManager migration, default changes, and customization/audit hooks.
## The Spring Security 6 model `@EnableMethodSecurity` wires up **Spring AOP** method interceptors. When you enable the flags, Spring registers, per annotation type, an `AuthorizationManagerBeforeMethodInterceptor` (and, for @PostAuthorize/filters, *After* variants) backed by a specific `AuthorizationManager<MethodInvocation>`: - **@Secured** -> `SecuredAuthorizationManager`. Reads the annotation's authority strings and checks the `Authentication`'s authorities for a literal match (no prefixing). - **@RolesAllowed** -> `Jsr250AuthorizationManager`. Applies the **role prefix** (default `ROLE_`, from the `GrantedAuthorityDefaults` bean) to each declared role, then matches. Also understands `@PermitAll` and `@DenyAll` from JSR-250. - **@PreAuthorize** -> `PreAuthorizeAuthorizationManager`, which evaluates SpEL via a `MethodSecurityExpressionHandler`. Each manager returns an `AuthorizationDecision` (granted/denied). On deny, the interceptor throws `AccessDeniedException` *before* the target method executes (for @PreAuthorize/@Secured/@RolesAllowed — all pre-invocation). ## What changed from @EnableGlobalMethodSecurity The legacy `@EnableGlobalMethodSecurity` used a fundamentally different pipeline: - **AccessDecisionManager** + a list of **AccessDecisionVoters** (`RoleVoter`, `Jsr250Voter`, `PreInvocationAuthorizationAdviceVoter`, etc.), combined by an `AffirmativeBased`/`ConsensusBased`/`UnanimousBased` strategy. Each voter returned ACCESS_GRANTED / DENIED / ABSTAIN and the manager aggregated votes. - `prePostEnabled` **defaulted to false**, so many people forgot to enable @PreAuthorize. The new `@EnableMethodSecurity` (Spring Security 5.6+, the standard in 6): - Drops voters/`AccessDecisionManager` in favor of composable `AuthorizationManager`s. - **Defaults `prePostEnabled` to true**; `securedEnabled` and `jsr250Enabled` still default to false. - Uses `AuthorizationManager` uniformly, which is easier to customize and compose (you can `AuthorizationManagers.allOf(...)`/`anyOf(...)`). ## Interception mechanics & gotchas - **Proxy-based**: checks apply only when the call goes through the Spring proxy. An internal `this.secured()` self-invocation **bypasses** the check. Cross-bean calls are fine. - **Public methods by default**: private/protected/package methods aren't advised unless you switch to AspectJ mode. Final classes/methods can't be CGLIB-proxied. - **Ordering**: interceptor order matters if you combine annotations; Spring assigns deterministic orders, and you can register custom `AuthorizationManagerBeforeMethodInterceptor` beans with explicit `order`. - **Observability**: you can wrap the manager to emit `AuthorizationEvent`s / audit denials. ## Practical customization example ```java @Configuration @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) class MethodSecurityConfig { @Bean static GrantedAuthorityDefaults roleDefaults() { return new GrantedAuthorityDefaults("ROLE_"); // affects @RolesAllowed & hasRole, not @Secured } } ``` ## Why a principal cares Understanding that all three annotations funnel through the same `AuthorizationManager` abstraction clarifies that they're **equivalent in enforcement**, differ only in expressiveness, and can be uniformly wrapped for auditing, testing (`AuthorizationManager` is easy to unit-test), and migration off the deprecated voter model.
- Why does an internal self-call to an @Secured method skip the check?Enforcement lives in the AOP proxy. A this.method() call doesn't go through the proxy, so the interceptor never fires. Only calls crossing the proxy boundary are advised.
- What replaced AccessDecisionManager and voters in Spring Security 6?The AuthorizationManager abstraction. @EnableMethodSecurity registers AuthorizationManagerBeforeMethodInterceptor beans backed by SecuredAuthorizationManager, Jsr250AuthorizationManager, and PreAuthorizeAuthorizationManager instead of voter aggregation.
- What's a default that flipped between the old and new enable annotations?prePostEnabled: it was false under @EnableGlobalMethodSecurity but defaults to true under @EnableMethodSecurity. securedEnabled/jsr250Enabled remain false by default in both.
saying these in an interview costs you the question
- Thinking @Secured/@RolesAllowed still use AccessDecisionVoters in Spring Security 6
- Assuming self-invocation is still protected
- Believing prePostEnabled defaults are the same across both enable annotations