Spring Security Roles and Permissions

In our last article, we talked about Granted Authority vs Role. In this article, we will look at the Spring security roles and privileges and how to use this feature to build your application.

Spring Security Roles and Permissions

There are multiple parts of an enterprise application, and it allows not all users to access the entire application. We may come up with requirement where we like to provide access to the application based on the user roles and privileges. Let’s take example of a simple back-end application managing the commerce store.

  1. The user with ADMIN role will have full permission to perform any action.
  2. A customer service agent can read customer and order information but doesn’t see other options.
  3. A product manager can only see options to update/ create products.

Spring security makes it more easy to build these types of rules using the roles and privileges. We can assign roles and privileges to the user during registration/ creation and these roles.In this article, we will see how to use the Spring security roles and privileges feature to handle such use cases. To make sure we have common understanding, let’s look at few important terms.

  • Roles – Role represents high-level role in system (e.g. ADMIN, MANAGER etc.), Each role can have low-level privileges.
  • Privileges – The privileges define the low level authority for a Role (e.g. ADMIN can read/write/delete but MANAGER can only read/edit)

This article is part of our Spring Security course, you can download the complete application from our GitHub Repository.

1. Database Design

There are multiple way to design the spring security roles and permissions but one of the most common and flexible way is to build and roles and privileges module around user groups. As part of any application, put the users in some groups, let’s take the following example for better understanding:

  1. A frontend user should go to CUSTOMER Group.
  2. Back-end users can go to EMPLOYEE group.
  3. We can create another variation of backed user (e.g. ADMIN, MANAGER etc.)

We will use the same concept of our application. Each user of the application will be part of a certain group, and we will use these groups to drive the roles and permissions. Here is the database design for our application.

Spring Security Roles and Permissions
  1. Each user belongs to a certain group.
  2. Groups will be assigned to the user at registration/ creation time.
  3. principle_group defines all the groups available in the system (e.g. customer, admin etc.)

I will not cover the user entity, as we already cover this in the Registration with Spring Security and Spring Boot article.

1.1. UserGroup Entity

Here is our entity class for the UserGroup

@Entity
@Table(name = "principle_groups")
public class Group{
    
    //removed getter and setter to save space
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String code;
    private String name;

    @ManyToMany(mappedBy = "userGroups")
    private Set<UserEntity> users;
}

The Group is a simple JPA entity and contains information about the group name and code. The interesting part is the @ManyToMany relation with User entity. This many-to-many relations will create another database table for us (see above DB table diagram).

1.2. User Entity Class

Here is our UserEntity class. The highlighting point is the relation with the Group Entity.

@Entity
@Table(name = "user")
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    @Column(unique = true)
    private String email;
    private String password;
    private String token;
    private boolean accountVerified;
    private int failedLoginAttempts;
    private boolean loginDisabled;

    @OneToMany(mappedBy = "user")
    private Set<SecureToken> tokens;

    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    })
    @JoinTable(name = "user_groups",
            joinColumns [email protected](name = "customer_id"),
            inverseJoinColumns = @JoinColumn(name = "group_id"
    ))
    private Set<Group> userGroups= new HashSet<>();

    public Set<Group> getUserGroups() {
        return userGroups;
    }

    public void setUserGroups(Set<Group> userGroups) {
        this.userGroups = userGroups;
    }
}

We can fill the data in principle_groups table based on your requirement. We are filling the following 2 groups:

  1. Customer
  2. Admin
Spring Security Roles and Permissions

2. Adding Groups to Users

We base our entire logic to derive the spring security roles and permissions on the assigned group to the user. Let’s change the registration process to assign the user group to the user. We will add a minor change to our DefaultUserService. During the registration process, we will add the group to the user profile.

@Service("userService")
public class DefaultUserService implements UserService{

    @Autowired
    private UserRepository userRepository;

    @Autowired
    UserGroupRepository groupRepository;

    @Override
    public void register(UserData user) throws UserAlreadyExistException {
        if(checkIfUserExist(user.getEmail())){
            throw new UserAlreadyExistException("User already exists for this email");
        }
        UserEntity userEntity = new UserEntity();
        BeanUtils.copyProperties(user, userEntity);
        encodePassword(user, userEntity);
        updateCustomerGroup(userEntity);
        userRepository.save(userEntity);
        sendRegistrationConfirmationEmail(userEntity);

    }

    private void updateCustomerGroup(UserEntity userEntity){
        Group group= groupRepository.findByCode("customer");
        userEntity.addUserGroups(group);
    }
}

You can always change the group assignment logic based on your requirement. We can even build logic in our back-end system to assign groups to the user.

3. Custom UserDetailsService

I have already explained the importance of UserDetailsService class in Spring security. The UserDetailsService is a core interface in Spring Security framework, which is used to retrieve the user’s authentication and authorization information. This interface is also responsible to provide the User’s GrantedAuthority list, which is used to derive our spring security roles and permissions for the user. Let’s change spring security custom UserDetailsService to return list of GrantedAuthority based on user groups.

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

    @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        final UserEntity customer = userRepository.findByEmail(email);
        if (customer == null) {
            throw new UsernameNotFoundException(email);
        }
        boolean enabled = !customer.isAccountVerified(); // we can use this in case we want to activate account after customer verified the account
        UserDetails user = User.withUsername(customer.getEmail())
                .password(customer.getPassword())
                .disabled(customer.isLoginDisabled())
                .authorities(getAuthorities(customer)).build()
                ;

        return user;
    }

    private Collection<GrantedAuthority> getAuthorities(UserEntity user){
        Set<Group> userGroups = user.getUserGroups();
        Collection<GrantedAuthority> authorities = new ArrayList<>(userGroups.size());
        for(Group userGroup : userGroups){
            authorities.add(new SimpleGrantedAuthority(userGroup.getCode().toUpperCase()));
        }

        return authorities;
    }
}

The interesting thing to follow here is how we are building the GrantedAuthority entities.We are using a simple logic to build the GrantedAuthority list same as the assigned user groups. You can change / customize the logic to build more complex GrantedAuthorities.

4. Spring Security Authority Mapping

With this change in the UserDetailsService, we can start using the authorities to handle the visibility on the UI using hasAnyAuthority() or hasAuthority() method. Let’s look at the modified spring security configuration.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/login", "/register","/home")
            .permitAll()
            .antMatchers("/account/**").hasAnyAuthority("CUSTOMER", "ADMIN")
            .and()
            ...
}

Looking at the above configuration, we are telling Spring security to only allow a user with CUSTOMER and ADMIN authority to access the /account/** pattern.Remember, the user authorities are provided by the UserDetailsService. You can also use the same option to show/ hide the links based on the user roles. Here is a sample code using Spring security with Thymeleaf.

<ul class="navbar-nav ml-auto">
    <li class="dropdown user user-menu" sec:authorize="hasAnyAuthority('CUSTOMER', 'ADMIN')">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
            <span class="hidden-xs" sec:authentication="name"></span>
        </a>
        <ul class="dropdown-menu">
            <li class="user-header">
                <img th:src="@{/dist/img/avatar5.png}" class="img-circle" alt="User Image">
                <p>
                    Spring Security Course
                    <small>Java Development Journal</small>
                </p>
            </li>
            <li class="user-footer">
                <div class="pull-right">
                    <a href="javascript: document.logoutForm.submit()" class="btn btn-default btn-flat">Sign out</a>
                </div>
            </li>
        </ul>
    </li>
    <form name="logoutForm" th:action="@{/logout}" method="post" th:hidden="true">
        <input hidden type="submit" value="Sign Out"/>
    </form>
</ul>

The interesting part is sec:authorize="hasAnyAuthority('CUSTOMER', 'ADMIN'), which shows that only user with authority of CUSTOMER and ADMIN can access this section. For other user, this will not be available.

We will cover more Granular level authorization (e.g. read/ write etc.) in our next article.

Summary

In this section we learned the spring security roles and permissions workflow and architecture. We learned how to build the roles and permissions around the user groups and how to use the custom UserDetailsService to build the GrantedAuthority list. The source code for this article will be available on our GitHub repository.