I just announced the newSpring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY

1. Overview

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

2. User Role and Privilege

First, let’s start with our entities. We have three main entities:

  • the User
  • the Role – this 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:

@Entity
public class User {
 
    @Id
    @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;

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

As you can see, the user contains the roles, but also a few additional details that are necessary for a proper registration mechanism.

Next – here’s the role:

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

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

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

And finally the privilege:

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

    private String name;

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

As you can see, we’re considering both the User <-> Role as well as the Role <-> Privilege relationships 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:

@Component
public class InitialDataLoader implements
  ApplicationListener<ContextRefreshedEvent> {

    boolean alreadySetup = false;

    @Autowired
    private UserRepository userRepository;
 
    @Autowired
    private RoleRepository roleRepository;
 
    @Autowired
    private PrivilegeRepository privilegeRepository;
 
    @Autowired
    private PasswordEncoder passwordEncoder;
 
    @Override
    @Transactional
    public void onApplicationEvent(ContextRefreshedEvent event) {
 
        if (alreadySetup)
            return;
        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.setFirstName("Test");
        user.setLastName("Test");
        user.setPassword(passwordEncoder.encode("test"));
        user.setEmail("[email protected]");
        user.setRoles(Arrays.asList(adminRole));
        user.setEnabled(true);
        userRepository.save(user);

        alreadySetup = true;
    }

    @Transactional
    private Privilege createPrivilegeIfNotFound(String name) {
 
        Privilege privilege = privilegeRepository.findByName(name);
        if (privilege == null) {
            privilege = new Privilege(name);
            privilegeRepository.save(privilege);
        }
        return privilege;
    }

    @Transactional
    private Role createRoleIfNotFound(
      String name, Collection<Privilege> privileges) {
 
        Role role = roleRepository.findByName(name);
        if (role == null) {
            role = new Role(name);
            role.setPrivileges(privileges);
            roleRepository.save(role);
        }
        return role;
    }
}

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

  • we’re creating the privileges
  • we’re creating the roles and assigning the privileges to them
  • 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, depending on how many contexts you have configured in your application – the ContextRefreshedEvent may be fired multiple times. And we only want the setup to be executed once.

Two quick notes here – first, about 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. Not a problem for the implementation of course, but 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 if you’re not explicitly changing it, it’s going to be required.

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:

@Service("userDetailsService")
@Transactional
public class MyUserDetailsService implements UserDetailsService {

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

    @Override
    public UserDetails loadUserByUsername(String email)
      throws UsernameNotFoundException {
 
        User user = userRepository.findByEmail(email);
        if (user == null) {
            return new org.springframework.security.core.userdetails.User(
              " ", " ", true, true, true, true, 
              getAuthorities(Arrays.asList(
                roleRepository.findByName("ROLE_USER"))));
        }

        return new org.springframework.security.core.userdetails.User(
          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) {
            collection.addAll(role.getPrivileges());
        }
        for (Privilege item : collection) {
            privileges.add(item.getName());
        }
        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 – you 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. 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 assigns Roles (and Privileges) to it – let’s now take a look at how this needs to be done during registration of a new user:

@Override
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();

    user.setFirstName(accountDto.getFirstName());
    user.setLastName(accountDto.getLastName());
    user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
    user.setEmail(accountDto.getEmail());

    user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER")));
    return repository.save(user);
}

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

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

6. Conclusion

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

The full implementation of this Registration with Spring Security tutorial can be found in the GitHub project – this is a Maven-based project, so it should be easy to import and run as it is.

I just announced the new Spring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY

Sort by:   newest | oldest | most voted
jirkapinkas
Guest

Why not instead of implementing ApplicationListener create method annotated with PostConstruct annotation? Since the bean is singleton, it will work the same way, right?

Eugen Paraschiv
Guest

Sure, @PostConstruct (or perhaps an InitializingBean) would also work. The main reason I went with the event was that I wanted to jump once the entire context is bootstrapped, not just that particular bean. However, it’s not much of a difference, since I’m just writing some data – so yes, that would be fine as well. Cheers,
Eugen.

arusland
Guest

The article would be complete if you were shown example of using privileges.

Eugen Paraschiv
Guest

Hey Arusland – thanks for the feedback. The privileges are actually used – if you look at MyUserDetailsService – getPrivileges – that’s where the privileges are converted into simple authorities. Spring Security then uses these authorities. That’s all the privileges do really – they’re authorities to be used. Hope that clarifies things. Cheers,
Eugen.

arusland
Guest

Ok, thanks)

aelix
Guest

Wonderful article. You should mention that the @Secured annotation won’t work because your privileges are not prefixed with ROLE_. The RoleVoter will ignore them. So you must explicitly use @PreAuthorize(“hasAuthority(‘READ_PRIVILEGE’)”) instead, or do what I did and include the role names in the granted authorities list in getPrivileges() so you can mix/match both.

Eugen Paraschiv
Guest

Hey Aelix – that’s a good point. I usually change that default prefix, but it wasn’t mentioned in the article, so I fixed that with a few extra notes about the mechanism. Thanks for pointing it out. Cheers,
Eugen.

Vineet Tyagi
Guest

In Spring Security 4 you need not to have ROLE_ prefix.

Eugen Paraschiv
Guest

That’s a good point – did some quick JIRA digging and that’s definitely the case. However, it looks like after the change, there are some (small) bugs if the prefix is missing – which are getting ironed out with new releases. So – it may still be worth keeping the prefix for now. Cheers and thanks for the very useful piece of feedback.
Eugen.

Michael Tabak
Guest

Isn’t this basically because as of Spring Security 3.1 (I think that’s the version) hasRole() will prepend “ROLE_” to whatever you pass in if “ROLE_” is not already present? Is there any other Spring change that affects this?

Eugen Paraschiv
Guest

There shouldn’t be other things that are affected. But – when I was looking into it a couple of months ago, I noticed some JIRAs that were related to this change, so it’s clear that there was some impact after the “ROLE_” prefix became not required. That’s why I suggested still using it for a while, until we’re sure that there’s no other problem associated with it.

AaronA
Guest

Where has the code gone?

Eugen Paraschiv
Guest

Hey Aaron – I moved it into a new repo and updated most links, but I missed this one. Should be good now – thanks for letting me know. Cheers,
Eugen.

Bui DucCanh
Guest

Hi, the first thanks useful article.
I want to build some admin pages (manage roles, permissions) to create new admin, moderator, create new role from permissions…Where should I declare: ROLE, PERMISSION? A Controller should manage by 1 ROLE with permissions: CREATE, VIEW, UPDATE, DELETE, EXPORT…?
Thanks u!
P/s: I’m a Java, Spring beginner.

Eugen Paraschiv
Guest

Hey Bui,
That’s an interesting question, but it’s also a very high level one and there are many ways you could design your domain. You could manage child objects through the parent or you could do it on their own – and there are advantages and disadvantages to both approaches.
So, unfortunately, this isn’t one where you’ll get a simple, right/wrong answer. But I hope that sets you on the right path of finding that answer for your particular scenario.
Cheers,
Eugen.

wpDiscuz