skip to content

How does Spring Security 6 process @Secured and @RolesAllowed under the hood, and what changed from @EnableGlobalMethodSecurity?

level: principalimportance: nice to knowfreq 25%

answer

  1. SecuredAuthorizationManager / Jsr250AuthorizationManager
  2. AuthorizationManager replaced voters/AccessDecisionManager
  3. prePostEnabled default true in SS6
  4. proxy-based, self-invocation bypasses
  5. Jsr250 uses GrantedAuthorityDefaults prefix

basics

~10 s

In 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
java
@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

for a junior

Know they run via a proxy interceptor before the method executes.

for a middle

Name @EnableMethodSecurity and that self-invocation bypasses checks.

for a senior

Identify the AuthorizationManager implementations and the JSR-250 prefix source.

for a principal

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

context