Course – LSS – NPI (cat=Spring Security)
announcement - icon

If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:


1. Overview

This tutorial continues the Registration with Spring Security series with a look at how to properly implement Roles and Privileges.

Further reading:

Intro to Spring Security Expressions

Simple and practical guide to Spring Security Expressions.

Introduction to Spring Method Security

A guide to method-level security using the Spring Security framework.

Spring Security - Redirect to the Previous URL After Login

A short example of redirection after login in Spring Security

2. User, Role and Privilege

Let’s start with our entities. We have three main entities:

  • The User
  • The Role represents the high-level roles of the user in the system. Each role will have a set of low-level privileges.
  • The Privilege represents a low-level, granular privilege/authority in the system.

Here’s the user:

public class User {
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;
    private String password;
    private boolean enabled;
    private boolean tokenExpired;

        name = "users_roles", 
        joinColumns = @JoinColumn(
          name = "user_id", referencedColumnName = "id"), 
        inverseJoinColumns = @JoinColumn(
          name = "role_id", referencedColumnName = "id")) 
    private Collection<Role> roles;

As we can see, the user contains the roles as well as a few additional details that are necessary for a proper registration mechanism.

Next, here’s the role:

public class Role {
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    @ManyToMany(mappedBy = "roles")
    private Collection<User> users;

        name = "roles_privileges", 
        joinColumns = @JoinColumn(
          name = "role_id", referencedColumnName = "id"), 
        inverseJoinColumns = @JoinColumn(
          name = "privilege_id", referencedColumnName = "id"))
    private Collection<Privilege> privileges;

Finally, let’s look at the privilege:

public class Privilege {
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "privileges")
    private Collection<Role> roles;

As we can see, we’re considering both the User <-> Role as well as the Role <-> Privilege relationships to be many-to-many bidirectional.

3. Setup Privileges and Roles

Next, let’s focus on doing some early setup of the Privileges and Roles in the system.

We’ll tie this to the startup of the application, and we’ll use an ApplicationListener on ContextRefreshedEvent to load our initial data on server start:

public class SetupDataLoader implements
  ApplicationListener<ContextRefreshedEvent> {

    boolean alreadySetup = false;

    private UserRepository userRepository;
    private RoleRepository roleRepository;
    private PrivilegeRepository privilegeRepository;
    private PasswordEncoder passwordEncoder;
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (alreadySetup)
        Privilege readPrivilege
          = createPrivilegeIfNotFound("READ_PRIVILEGE");
        Privilege writePrivilege
          = createPrivilegeIfNotFound("WRITE_PRIVILEGE");
        List<Privilege> adminPrivileges = Arrays.asList(
          readPrivilege, writePrivilege);
        createRoleIfNotFound("ROLE_ADMIN", adminPrivileges);
        createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege));

        Role adminRole = roleRepository.findByName("ROLE_ADMIN");
        User user = new User();
        user.setEmail("[email protected]");

        alreadySetup = true;

    Privilege createPrivilegeIfNotFound(String name) {
        Privilege privilege = privilegeRepository.findByName(name);
        if (privilege == null) {
            privilege = new Privilege(name);
        return privilege;

    Role createRoleIfNotFound(
      String name, Collection<Privilege> privileges) {
        Role role = roleRepository.findByName(name);
        if (role == null) {
            role = new Role(name);
        return role;

So, what’s happening during this simple setup code? Nothing complicated:

  • We’re creating the privileges.
  • Then we’re creating the roles and assigning the privileges to them.
  • Finally, we’re creating a user and assigning a role to it.

Note how we’re using an alreadySetup flag to determine if the setup needs to run or not. This is simply because the ContextRefreshedEvent may be fired multiple times depending on how many contexts we have configured in our application. And we only want to run the setup once.

Two quick notes here. We’ll first look at terminology. We’re using the Privilege – Role terms here. But in Spring, these are slightly different. In Spring, our Privilege is referred to as Role and also as a (granted) authority, which is slightly confusing.

This is not a problem for the implementation of course, but it’s definitely worth noting.

Second, these Spring Roles (our Privileges) need a prefix. By default, that prefix is “ROLE”, but it can be changed. We’re not using that prefix here, just to keep things simple, but keep in mind that it will be required if we’re not explicitly changing it.

4. Custom UserDetailsService

Now let’s check out the authentication process.

We’re going to see how to retrieve the user within our custom UserDetailsService and how to map the right set of authorities from the roles and privileges the user has assigned:

public class MyUserDetailsService implements UserDetailsService {

    private UserRepository userRepository;
    private IUserService service;
    private MessageSource messages;
    private RoleRepository roleRepository;

    public UserDetails loadUserByUsername(String email)
      throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email);
        if (user == null) {
            return new
              " ", " ", true, true, true, true, 

        return new
          user.getEmail(), user.getPassword(), user.isEnabled(), true, true, 
          true, getAuthorities(user.getRoles()));

    private Collection<? extends GrantedAuthority> getAuthorities(
      Collection<Role> roles) {
        return getGrantedAuthorities(getPrivileges(roles));

    private List<String> getPrivileges(Collection<Role> roles) {
        List<String> privileges = new ArrayList<>();
        List<Privilege> collection = new ArrayList<>();
        for (Role role : roles) {
        for (Privilege item : collection) {
        return privileges;

    private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String privilege : privileges) {
            authorities.add(new SimpleGrantedAuthority(privilege));
        return authorities;

The interesting thing to follow here is how the Privileges (and Roles) are mapped to GrantedAuthority entities.

This mapping makes the entire security configuration highly flexible and powerful. We can mix and match roles and privileges as granular as necessary, and at the end, they’ll be correctly mapped to authorities and returned back to the framework.

5. Role Hierarchy

Additionally, let’s organize our roles into hierarchies.

We’ve seen how to implement role-based access control by mapping privileges to roles. This allows us to assign a single role to a user rather than having to assign all the individual privileges.

However, as the number of roles increases, users might require multiple roles, leading to role explosion:

role explosion

To overcome this, we can use Spring Security’s role hierarchies:


Assigning the role ADMIN automatically gives the user the privileges of both the STAFF and USER roles.

However, a user with the role STAFF can only perform STAFF and USER role actions.

Let’s create this hierarchy in Spring Security by simply exposing a bean of type RoleHierarchy:

public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    String hierarchy = "ROLE_ADMIN > ROLE_STAFF \n ROLE_STAFF > ROLE_USER";
    return roleHierarchy;

We use the > symbol in the expression to define the role hierarchy. Here, we’ve configured the role ADMIN to include the role STAFF, which in turn includes the role USER.

To include this role hierarchy in Spring Web Expressions, we add the roleHierarchy instance to the WebSecurityExpressionHandler:

public DefaultWebSecurityExpressionHandler customWebSecurityExpressionHandler() {
    DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
    return expressionHandler;

Finally, add  the expressionHandler into http.authorizeRequests():

    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                .antMatchers(HttpMethod.GET, "/roleHierarchy")

The endpoint /roleHierarchy is protected with ROLE_STAFF in order to prove that the webSecurityExpressionHandler is working.

As we can see, role hierarchies are a great way to reduce the number of roles and authorities we need to add to a user.

6. User Registration

Finally, let’s take a look at registration for a new user.

We’ve seen how setup goes about creating the User and assigning Roles (and Privileges) to it.

Let’s now take a look at how this needs to be done during registration of a new user:

public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {
    if (emailExist(accountDto.getEmail())) {
        throw new EmailExistsException
          ("There is an account with that email adress: " + accountDto.getEmail());
    User user = new User();



In this simple implementation, since we’re assuming that a standard user is being registered, we’re assigning it the ROLE_USER role.

Of course, more complex logic can easily be implemented in the same way, either by having multiple, hard-coded registration methods or by allowing the client to send the type of user that’s being registered.

7. Conclusion

In this article, we illustrated how to implement Roles and Privileges with JPA, for a Spring Security-backed system.

We also configured a role hierarchy to simplify our access control configuration.

The full implementation of this Registration with Spring Security tutorial can be found over on GitHub.

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

res – Security (video) (cat=Security/Spring Security)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.