What are the semantics when @Secured or @RolesAllowed list multiple roles, and what can't they express?
answer
- multiple values = OR / any-of
- no AND, no NOT, no args
- adding entries only widens access
- AND -> @PreAuthorize hasRole and hasRole
- set-intersection test
basics
~20 sMultiple listed roles mean OR — having any one of them grants access. Neither annotation can express AND (require several roles at once), negation, or any condition based on method arguments. For those you need @PreAuthorize with SpEL.
solid answer
~40 sBoth @Secured({"ROLE_A","ROLE_B"}) and @RolesAllowed({"A","B"}) use OR / any-of semantics: the caller passes if they hold at least one of the listed authorities. There is no way to require multiple roles simultaneously (AND), to negate a role (NOT), or to reference method parameters — the check is a pure set-membership test against the user's GrantedAuthority collection. If you need 'ADMIN and MFA_VERIFIED', ownership checks, or scope+role combinations, you must switch to @PreAuthorize("hasRole('ADMIN') and hasRole('MFA')") or a custom expression. A subtle point: since they're OR-only, a longer list only ever widens access — it can never narrow it. This makes reviewing them easy but also means you can't tighten a rule by adding entries.
code
java · 7 lines// any-of: admin OR support may call
@Secured({"ROLE_ADMIN", "ROLE_SUPPORT"})
public void handleTicket(Long id) { ... }
// need BOTH roles? annotations can't -> use SpEL
@PreAuthorize("hasRole('ADMIN') and hasRole('AUDITOR')")
public void closeQuarter() { ... }go deeper
Remember multiple roles = any one is enough (OR).
State clearly it's OR-only and name AND/NOT/args as the gaps needing @PreAuthorize.
Note the 'adding entries only widens access' review property.
Use the OR-only limitation to decide where a codebase must standardize on @PreAuthorize.
## OR semantics When you supply multiple values, both annotations grant access if the user has **any** of them: ```java @Secured({"ROLE_ADMIN", "ROLE_SUPPORT"}) // admin OR support @RolesAllowed({"ADMIN", "SUPPORT"}) // admin OR support ``` Internally the check is: *does the user's authority set intersect the annotation's set?* One match is enough. ## What they cannot express 1. **AND (conjunction).** You cannot say 'must be ADMIN *and* AUDITOR'. Listing both only OR's them. Use `@PreAuthorize("hasRole('ADMIN') and hasRole('AUDITOR')")`. 2. **NOT (negation).** No 'must not be GUEST'. Use SpEL `!hasRole('GUEST')`. 3. **Argument-based rules.** No access to `#id`, the principal's fields, or method parameters. SpEL only. 4. **Custom permissions / bean calls.** No `hasPermission(...)` or `@service.check(#x)`. 5. **Post-invocation / filtering.** No @PostAuthorize/@PreFilter/@PostFilter analog. ## Consequence for review and evolution Because the list is OR-only, **adding an entry can only broaden access, never restrict it**. That's a nice property for reasoning about who's allowed, but it also means you can't 'tighten' an @Secured rule by editing its list — tightening to an AND requires moving to @PreAuthorize. Reviewers should treat every added role as a new door opened. ## Empty / misused values - An empty `@RolesAllowed({})` effectively allows no one (no authority can match), which is rarely intended. - Duplicated or misspelled entries don't error; they just never match, so a typo silently reduces who's allowed to the remaining valid entries. ## Practical example of the boundary ```java // OR — fine with @RolesAllowed @RolesAllowed({"ADMIN", "BILLING"}) public Invoice view(Long id) { ... } // AND + argument — must be @PreAuthorize @PreAuthorize("hasRole('ADMIN') and #invoice.orgId == authentication.principal.orgId") public void approve(Invoice invoice) { ... } ```
- How do you require a user to hold two roles simultaneously?You can't with @Secured/@RolesAllowed — they're OR-only. Use @PreAuthorize("hasRole('A') and hasRole('B')").
- If you add a role to an existing @Secured list, can that ever restrict access?No. OR semantics mean adding an entry can only widen who's allowed, never narrow it.
saying these in an interview costs you the question
- Believing multiple listed roles are AND-ed (all required)
- Thinking you can negate a role inside @Secured/@RolesAllowed
- Expecting to reference method arguments in these annotations