skip to content

Roles, Authorities & SpEL Access

hasRole silently prepends ROLE_ while hasAuthority does not, and access() lets you drop into SpEL for anything more complex. The ROLE_ prefix trips up almost everyone, which is exactly why it is asked.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

4

What is the difference between hasRole and hasAuthority in Spring Security, and how does the ROLE_ prefix affect them?

level: juniorimportance: must knowfreq 82%

answer

  1. hasRole adds ROLE_, hasAuthority is literal
  2. role = authority starting with ROLE_
  3. hasRole('ADMIN') == hasAuthority('ROLE_ADMIN')
  4. roles() prepends, authorities() literal
  5. GrantedAuthorityDefaults changes prefix

basics

~10 s

hasRole('ADMIN') automatically checks for the authority 'ROLE_ADMIN' by adding the ROLE_ prefix. hasAuthority('ROLE_ADMIN') checks the exact string you pass, with no prefix added. They match the same authority only if you include ROLE_ yourself.

solid answer

~40 s

In Spring Security every permission is a GrantedAuthority — just a String. Roles are a naming convention: authorities that start with 'ROLE_'. hasRole('ADMIN') is a convenience method that prepends the default 'ROLE_' prefix and then checks for 'ROLE_ADMIN'. hasAuthority('ROLE_ADMIN') does an exact match with no prefix logic. So hasRole('ADMIN') and hasAuthority('ROLE_ADMIN') are equivalent, while hasRole('ROLE_ADMIN') would wrongly look for 'ROLE_ROLE_ADMIN'. The common gotcha is loading users whose authorities lack the ROLE_ prefix (e.g. just 'ADMIN') and then using hasRole — the check silently fails. Use hasAuthority for fine-grained permissions like 'user:read', and hasRole for coarse roles. The prefix itself is configurable via a GrantedAuthorityDefaults bean.

code

java · 17 lines
java
// Storing users
UserDetails admin = User.withUsername("a")
    .password("{noop}pw")
    .roles("ADMIN")        // stored as authority "ROLE_ADMIN"
    .build();

UserDetails svc = User.withUsername("s")
    .password("{noop}pw")
    .authorities("user:read") // literal, NO prefix
    .build();

// Checks
http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admin/**").hasRole("ADMIN")           // -> ROLE_ADMIN
    .requestMatchers("/api/users").hasAuthority("user:read") // exact match
    // .requestMatchers("/x").hasRole("ROLE_ADMIN")  // BUG: needs ROLE_ROLE_ADMIN
);

go deeper

for a junior

Must know hasRole auto-adds ROLE_ and hasAuthority does not; know the double-prefix bug.

for a middle

Should connect it to how UserDetails authorities are stored (roles() vs authorities()) and the silent-403 gotcha.

for a senior

Explains GrantedAuthority as a plain String, when to model permissions vs roles, and GrantedAuthorityDefaults.

for a principal

Frames role-vs-permission modeling as an architectural choice and understands global prefix implications across method + web security.

## Core concepts **GrantedAuthority** is the fundamental authorization primitive in Spring Security. It is an interface with a single meaningful method, `String getAuthority()`, that returns a permission as a plain String (e.g. `"ROLE_ADMIN"`, `"user:read"`). The standard implementation is `SimpleGrantedAuthority`. An `Authentication` (the logged-in principal) carries a `Collection<? extends GrantedAuthority>`. Spring Security makes **no built-in distinction between a 'role' and an 'authority'** at the storage level — both are just authority strings. A **role** is merely a convention: an authority whose name starts with the prefix `ROLE_`. ## hasRole vs hasAuthority - `hasRole("ADMIN")` — a convenience check that **prepends the configured role prefix** (default `"ROLE_"`) and then verifies the current user has the authority `"ROLE_ADMIN"`. - `hasAuthority("ROLE_ADMIN")` — an **exact-string** check; it does not add or strip any prefix. Therefore: - `hasRole("ADMIN")` ≡ `hasAuthority("ROLE_ADMIN")`. - `hasRole("ROLE_ADMIN")` is a **bug** — it checks for `"ROLE_ROLE_ADMIN"`. ## Where the prefix is applied The prefix is added at the *check* side (the expression/AuthorizationManager), not automatically at the *storage* side. When you build a `UserDetails` you must store the authority **with** the prefix if you want `hasRole` to find it. `User.withUsername(...).roles("ADMIN")` conveniently adds `ROLE_` for you, producing `ROLE_ADMIN`; but `.authorities("ADMIN")` stores the literal `"ADMIN"`, which `hasRole("ADMIN")` will **not** match. ## Changing the prefix Expose a `GrantedAuthorityDefaults` bean to change the global prefix: ```java @Bean static GrantedAuthorityDefaults grantedAuthorityDefaults() { return new GrantedAuthorityDefaults(""); // no prefix, or e.g. "PERM_" } ``` Note it should be a `static` bean so it is initialized early enough to affect security infrastructure. ## Method-security equivalents The same semantics apply to annotations: `@PreAuthorize("hasRole('ADMIN')")` vs `@PreAuthorize("hasAuthority('ROLE_ADMIN')")`, and the older `@Secured("ROLE_ADMIN")` (which requires the full prefixed string). ## When to use which - Use **hasRole / roles** for coarse-grained, business roles: ADMIN, USER, MANAGER. - Use **hasAuthority / authorities** for fine-grained permissions: `"invoice:approve"`, `"user:read"`. These typically don't use the ROLE_ prefix. ## Common gotchas 1. Storing authorities without `ROLE_` then calling `hasRole` → silent 403. 2. Passing `ROLE_` into `hasRole` → double prefix. 3. Mixing `roles()` (adds prefix) and `authorities()` (literal) inconsistently. 4. Assuming a custom prefix affects only some checks — `GrantedAuthorityDefaults` changes it globally.

  • A user is loaded with authority 'ADMIN' (no prefix) and /admin/** uses hasRole('ADMIN'). Why do they get 403, and how do you fix it?
    hasRole('ADMIN') checks for 'ROLE_ADMIN', but the stored authority is 'ADMIN', so no match. Fix by storing 'ROLE_ADMIN' (use roles('ADMIN')), or switch the check to hasAuthority('ADMIN'), or remove the prefix globally with a GrantedAuthorityDefaults('') bean.
  • How do you globally change the ROLE_ prefix to something else?
    Register a static @Bean of type GrantedAuthorityDefaults with the desired prefix, e.g. new GrantedAuthorityDefaults("PERM_"). It must be static so it's created before the security expression infrastructure.

context

open as a page

How does hasAnyRole work, and what is a GrantedAuthority in Spring Security?

level: middleimportance: should knowfreq 58%

basics

~10 s

GrantedAuthority is an interface representing one permission as a String via getAuthority(). hasAnyRole('ADMIN','MANAGER') grants access if the user has ANY of the listed roles, prepending ROLE_ to each — equivalent to hasAnyAuthority('ROLE_ADMIN','ROLE_MANAGER').

open as a page

How do you apply a SpEL access rule to an HTTP request in Spring Security 6, including IP-based restrictions with hasIpAddress?

level: seniorimportance: should knowfreq 47%

basics

~10 s

In Spring Security 6 the String access("...") method was removed. Pass an AuthorizationManager to access(...). Wrap a SpEL string in WebExpressionAuthorizationManager, e.g. access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasIpAddress('10.0.0.0/8')")).

open as a page

As an architect, how do you decide between roles, granular authorities, SpEL expressions, and custom AuthorizationManagers — and what are the maintainability trade-offs?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Use coarse roles for broad access tiers and granular authorities for fine permissions. Use SpEL for simple declarative combinations, but move complex, testable, or data-dependent logic into a custom AuthorizationManager. Avoid embedding business rules in stringly-typed SpEL.

open as a page