CodeGym /Kursy /Spring Security /ROLE_: roles and authorities

ROLE_: roles and authorities

Spring Security
Poziom 7, Lekcja 3
Dostępny

1. The meaning of the ROLE_ prefix

When you first see ROLE_ADMIN, you may want to ask: “Is this the admin password? Or a secret sign on the door to the server room?” In reality, it is much more prosaic: it is a naming convention. Spring Security works with permission strings internally, and roles have historically been agreed to be stored as ordinary “authorities”, but with the ROLE_ prefix.

By this point, we already have the role model for zones and point-specific authorities for actions. One last source of confusion remains: why the same access looks like ADMIN in one place and like ROLE_ADMIN in another.

In Spring Security there is the concept of GrantedAuthority — literally, “a granted right.” And almost everywhere access is involved, strings are compared in the end. A role is not a separate entity floating in a vacuum; it is just an authority with a special “label” in the form of a prefix. So ROLE_ADMIN is a string that means: “the user has the ADMIN role.”

It is important to remember one key reading rule: if you see ROLE_..., that is not a “different” permission system; it is the same permission list, only with the role marked by a prefix. And the hasRole(...) methods exist in part for convenience, so that you can write the human-readable role name without ROLE_, while Spring Security adds the prefix automatically. The official documentation explicitly says that hasRole is a shortcut for checking an authority with the automatic ROLE_ prefix.

A small “back-of-the-envelope” example to see what an authority as a string looks like:

import org.springframework.security.core.authority.SimpleGrantedAuthority;

// Authority in Spring Security is literally a string that is later compared during access checks
SimpleGrantedAuthority a = new SimpleGrantedAuthority("ROLE_ADMIN");

// The same string we passed to the constructor is returned
System.out.println(a.getAuthority()); // ROLE_ADMIN

There is no magic here: the “right” is the string "ROLE_ADMIN". After that, Spring Security compares such strings with what you requested in access rules.

2. Two modes: role-style and exact-string

The main source of confusion in this topic is that Spring Security gives you two “styles,” and they look similar, but behave differently. It is like two modes of the same tool: one does a small piece of work for you, the other takes everything literally. If you mix them up, you get the situation “I am definitely an admin, but it won’t let me in,” and then the stage of “I will try once more and disable security” begins.

The first mode is role-style. You write the “pure role name,” for example ADMIN, and Spring Security understands on its own that it actually needs to compare against "ROLE_ADMIN". This applies to hasRole("ADMIN") and to roles("ADMIN") on the user builder.

The second mode is exact-string. You write the string “as is,” and Spring Security compares exactly that, without guessing. This applies to hasAuthority(...) and to authorities(...) when creating a user.

Important hint: hasRole is not “a check of some other entity.” It is the same GrantedAuthority, just with an automatic prefix.

To make it easier to read by eye, keep a mini-table in mind (as a “translator” from role-style to exact-string):

Where you write the rule What you write What is actually checked/stored
hasRole("ADMIN") ADMIN "ROLE_ADMIN"
hasAuthority("ROLE_ADMIN") ROLE_ADMIN "ROLE_ADMIN"
hasAuthority("draft:publish") draft:publish "draft:publish"

Pay attention: draft:publish is not a role at all. It is just a point-specific permission, and no ROLE_ is attached to it. It is compared as an ordinary string.

3. roles(...) and authorities(...) on a user

When we create in-memory users, we are effectively building a user “passport” for Spring Security by hand. And here it is important not to mix up which method expects which format. Beginners often think: “well, it is just a list of permissions, what is the difference?” There is a difference — and it is exactly in ROLE_.

If you use roles("ADMIN"), you write the role without a prefix, and Spring Security under the hood turns it into "ROLE_ADMIN". This is convenient when you want to store roles as roles and not see ROLE_ by hand.

If you use authorities("ROLE_ADMIN"), you place the string exactly as it will be stored and compared. This is convenient when you mix both roles and point-specific permissions in one user, because point-specific permissions are still written as exact strings.

Here is a minimal demonstration of the difference.

Variant A: role-style (the prefix is added automatically)

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

// roles("ADMIN") means: actually store authority "ROLE_ADMIN"
UserDetails admin = User.builder()
    .username("root")
    .password("{bcrypt}...") // this is just an example: in real code, store a valid hash
    .roles("ADMIN") // ROLE_ prefix will be added automatically
    .build();

Here the role is specified as "ADMIN", and inside Spring Security it will be turned into "ROLE_ADMIN".

Variant B: exact-string (you write everything as is)

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

// authorities(...) is exact-string: whatever you write is what will be in the permission list
UserDetails admin = User.builder()
    .username("root")
    .password("{bcrypt}...") // this is just an example: in real code, store a valid hash
    .authorities("ROLE_ADMIN") // you add the prefix yourself
    .build();

Here you are already responsible for the prefix. And that is normal. It is just important not to forget: if you wrote "ADMIN" in authorities(...), then it will be authority "ADMIN", not the role "ROLE_ADMIN".

By the way, in the official in-memory configuration examples, you can see that roles as authorities ultimately look exactly like this: ROLE_USER, ROLE_ADMIN.

4. hasRole(...) and hasAuthority(...) in SecurityFilterChain

In SecurityFilterChain, we write rules as if we were drawing a subway map: here is the public branch, here is the user branch, here is the editor branch, and here is the “admin station, careful, the doors close automatically.” And in this place, ROLE_ especially hurts when it shoots you in the foot: one wrong word — and you suddenly get a secret mode of “admin is not admin.”

The key fact: hasRole("ADMIN") automatically adds ROLE_ to what you passed. The documentation puts it very directly: a role must not start with ROLE_, because it will be automatically supplemented with this prefix anyway.

Practically, this means:

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http.authorizeHttpRequests(a -> a
        // hasRole("ADMIN") -> under the hood, this will check authority "ROLE_ADMIN"
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
        // hasAuthority(...) -> exact string, with no prefixes or guessing
        .requestMatchers("/api/editor/drafts/*/publish").hasAuthority("draft:publish")
    ).build();
}

The first line means: “allow only the user who has authority "ROLE_ADMIN".” The second line means: “allow only the user who has authority "draft:publish".”

And now — the most classic “it does not work, why?” variant:

// Incorrect: hasRole will add ROLE_ itself and the result will be ROLE_ROLE_ADMIN
.requestMatchers("/api/admin/**").hasRole("ROLE_ADMIN")

Why is it incorrect? Because hasRole will add the prefix again, and in fact it will search for "ROLE_ROLE_ADMIN". And yes, the user of course does not have such a permission (unless you decided to throw a masquerade).

A symmetric mistake is the other way around:

// Almost always incorrect: this checks authority "ADMIN", while the role is usually stored as "ROLE_ADMIN"
.requestMatchers("/api/admin/**").hasAuthority("ADMIN")

This will check authority "ADMIN", while your roles usually live as "ROLE_ADMIN". In other words, you asked for one thing, but the list contains another.

5. The algorithm for reading roles and authorities

Confusion with ROLE_ rarely comes from the complexity of Spring Security. It comes from the fact that our brain loves to “guess.” We see ADMIN and automatically think about an admin. But Spring Security does not think — it compares strings. So it is useful to have a small reading algorithm, like an experienced editor: “if the word is written like this, read it like this.”

Imagine that you open the configuration a month later and want to understand what is happening without running the application. You can literally follow the rule: if you see hasRole, mentally add ROLE_; if you see hasAuthority, treat the string as an exact value; if you see roles(...) — the prefix will be added automatically; if authorities(...) — there will be no prefix until you write it yourself.

Here is a simple flowchart that helps avoid “double prefix” and “missing prefix” issues:

flowchart TD
    A["I look at the access rule"] --> B{"hasRole(...) ?"}
    B -- Yes --> C["Passed value = role name without ROLE_"]
    C --> D["The framework will check ROLE_ + value"]
    B -- No --> E{"hasAuthority(...) ?"}
    E -- Yes --> F["Exact string is checked as is"]
    E -- No --> G["I look for another check (permitAll/authenticated/denyAll)"]

If this feels too “childish” as an algorithm, great. Exactly these things save you in a real project when you are fixing access in production on a Friday evening and very much want to keep living.

6. Naming does not change here; only the conversion of role to string changes

The conventions themselves are already fixed and do not require a new scheme. Roles remain USER / EDITOR / ADMIN, and point-specific authorities are exact strings of the resource:action type, for example draft:publish. The only new thing in this lecture is this: when a role is stored or checked as an authority string, it gets the ROLE_ prefix; point-specific authorities live as they are.

So the practical rule is simple. If you work through hasRole(...) or roles(...), write the human-readable role name without the prefix. If you work through hasAuthority(...) or authorities(...), write exactly the string that should be stored for the user: ROLE_ADMIN, ROLE_EDITOR, draft:publish. That is already enough to build a complete matrix without double prefixes and lost permissions.

7. Example: zones and point-specific permissions

Right now we have a learning, but already quite “product-like” picture: there is a public zone, a personal user zone, an editor zone, and an admin zone. For zones it is convenient to use roles: they are short and easy to read. But for individual actions (for example, publishing a draft), authority is sometimes more expressive, because the role EDITOR can be too broad.

Below is a small fragment that shows exactly this approach: zones through hasRole, and the point-specific action through hasAuthority. Pay attention to the order: put the point-specific publish rule above the general editor zone, otherwise the general matcher will intercept the request first. And one more important detail: in hasRole we pass ADMIN, not ROLE_ADMIN, because the prefix will be added automatically.

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http.authorizeHttpRequests(auth -> auth
        // We restrict application zones (routes) by roles
        .requestMatchers("/api/me/**").hasRole("USER")

        // Put the point-specific action above the general editor zone
        .requestMatchers("/api/editor/drafts/*/publish").hasAuthority("draft:publish")
        .requestMatchers("/api/editor/**").hasRole("EDITOR")

        // Admin zone — role ADMIN (the check will be against "ROLE_ADMIN")
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
    ).build();
}

And for this to actually work, the in-memory user must have exactly the strings that these checks expect. If you chose the exact-string style in authorities(...), then roles must be stored as ROLE_... (and that is fine):

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

// Here we explicitly set authorities as strings (exact-string)
UserDetails editor = User.builder()
    .username("eva")
    .password("{bcrypt}...") // this is just an example: in real code, store a valid hash
    // Store roles in the authorities list with ROLE_, and the point-specific permission as is
    .authorities("ROLE_USER", "ROLE_EDITOR", "draft:publish")
    .build();

And here everything reads without mystery: role strings start with ROLE_, while the point-specific permission has no prefix.

8. Common mistakes with ROLE_

Mistake #1: writing hasRole("ROLE_ADMIN").
This form seems logical because you have seen ROLE_ADMIN somewhere in logs or in authorities(...). But hasRole adds ROLE_ itself, and in the end the check turns into expecting ROLE_ROLE_ADMIN. The documentation explicitly warns that a role must not start with ROLE_, because the prefix is added automatically.

Mistake #2: writing hasAuthority("ADMIN") when the user stores the role as ROLE_ADMIN.
hasAuthority is an exact-string check. It does not “guess” that you meant a role, and it does not add ROLE_. If the user has "ROLE_ADMIN", and you check "ADMIN", then for Spring Security those are two different permissions. And it will honestly deny access, even though you “in your heart” consider the user an admin.

Mistake #3: mixing naming without an explicit agreement.
Sometimes a role is called ADMIN in one place in the project, admin in another, and ROLE_admin in a third. The strings become similar, but not identical. At some point, you start fixing access by “rearranging letters,” as if it were the Wi-Fi password in a cafe. It is better to agree once: roles are USER/EDITOR/ADMIN, and a role as an authority is ROLE_..., and then just keep the style.

Mistake #4: trying to “guess” what a method does instead of remembering that hasRole = shortcut.
If you think of hasRole as separate magic, you will inevitably get confused when you see hasAuthority("draft:publish") next to it. The correct picture is simpler: hasRole is just a more convenient way to write an authority check with the ROLE_ prefix.

1
Zadanie
Spring Security, poziom 7, lekcja 3
Niedostępne
`roles("ADMIN")` i `hasRole("ADMIN")` bez ręcznego `ROLE_`
`roles("ADMIN")` i `hasRole("ADMIN")` bez ręcznego `ROLE_`
1
Zadanie
Spring Security, poziom 7, lekcja 3
Niedostępne
Ta sama rola za pomocą `hasRole` i `hasAuthority`
Ta sama rola za pomocą `hasRole` i `hasAuthority`
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION