Spring Security Add Roles to User Examples
- Details
- Written by Nam Ha Minh
- Last Updated on 04 May 2021   |   Print Email
In this Spring Security tutorial, I’d love to share with you how to implement authorization by adding roles to users in a Java web application – from database design to entity classes; from unit tests to adding default role in user registration; to updating a user’s roles in web form.
Technologies: Spring Web MVC, Spring Data JPA, Hibernate framework, Spring Security, Spring Boot Test, JUnit 5, AssertJ, Thymeleaf and MySQL database.
Basically, we need to have 3 tables in the database like this:
A user can have one or more roles, and a role can be assigned to one or more users, thus the entity relationship between users and roles tables is many to many. The users_roles is an intermediate table that realizes this relationship.
1. Code for User and Role Entity Classes & Repositories
Code the User entity class as follows:
package net.codejava; import java.util.HashSet; import java.util.Set; import javax.persistence.*; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true, length = 45) private String email; @Column(nullable = false, length = 64) private String password; @Column(name = "first_name", nullable = false, length = 20) private String firstName; @Column(name = "last_name", nullable = false, length = 20) private String lastName; @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private Set<Role> roles = new HashSet<>(); public void addRole(Role role) { this.roles.add(role); } // getters and setters are not shown for brevity }
And code the Role entity class like this:
package net.codejava; import javax.persistence.*; @Entity @Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(nullable = false, length = 45) private String name; public Role() { } public Role(String name) { this.name = name; } public Role(Integer id, String name) { this.id = id; this.name = name; } public Role(Integer id) { this.id = id; } @Override public String toString() { return this.name; } // getters and setters are not shown for brevity }
As you can see, the User class has a Set of Roles but the Role class doesn’t have any references of User. And by default, no cascade operations on a @ManyToMany relationship – that means updating a User object won’t change the associated Role objects.
2. Unit Test – Create Roles
Next, let’s code the following test class for persisting some Role objects into the database:
package net.codejava; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.annotation.Rollback; @DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) @Rollback(false) public class RoleRepositoryTests { @Autowired private RoleRepository repo; @Test public void testCreateRoles() { Role user = new Role("User"); Role admin = new Role("Admin"); Role customer = new Role("Customer"); repo.saveAll(List.of(user, admin, customer)); List<Role> listRoles = repo.findAll(); assertThat(listRoles.size()).isEqualTo(3); } }
Run the testCreateRoles() method, we’ll end up having 3 new rows inserted to the roles table, according to 3 roles: User, Admin and Customer.
3. Unit Test – Add Roles to User
For testing add roles to users, create the UserRepositoryTests class with the following initial code:
package net.codejava; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.test.annotation.Rollback; @DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) @Rollback(false) public class UserRepositoryTests { @Autowired private TestEntityManager entityManager; @Autowired private UserRepository userRepo; @Autowired private RoleRepository roleRepo; // test methods go here... }
And the following is code snippet of the first test methods that persists a User object without any Roles:
@Test public void testCreateUser() { User user = new User(); user.setEmail("ravikumar@gmail.com"); user.setPassword("ravi2020"); user.setFirstName("Ravi"); user.setLastName("Kumar"); User savedUser = userRepo.save(user); User existUser = entityManager.find(User.class, savedUser.getId()); assertThat(user.getEmail()).isEqualTo(existUser.getEmail()); }
And the following test method creates a new user with Admin role:
@Test public void testAddRoleToNewUser() { Role roleAdmin = roleRepo.findByName("Admin"); User user = new User(); user.setEmail("mikes.gates@gmail.com"); user.setPassword("mike2020"); user.setFirstName("Mike"); user.setLastName("Gates"); user.addRole(roleAdmin); User savedUser = userRepo.save(user); assertThat(savedUser.getRoles().size()).isEqualTo(1); }
And the following test will update an existing user by adding two roles User and Customer:
@Test public void testAddRoleToExistingUser() { User user = userRepo.findById(1L).get(); Role roleUser = roleRepo.findByName("User"); Role roleCustomer = new Role(3); user.addRole(roleUser); user.addRole(roleCustomer); User savedUser = userRepo.save(user); assertThat(savedUser.getRoles().size()).isEqualTo(2); }
Run these test methods and you will see rows inserted into the users and users_roles tables. The roles table is not affected.
4. Set Default Role for User in Registration
A common scenario in user registration is setting the default role for a newly registered user, e.g. User or Customer role. Below is an example code snippet at service layer:
package net.codejava; @Service public class UserService { @Autowired private UserRepository userRepo; @Autowired RoleRepository roleRepo; @Autowired PasswordEncoder passwordEncoder; public void registerDefaultUser(User user) { Role roleUser = roleRepo.findByName("User"); user.addRole(roleUser); userRepo.save(user); } }
And the code at controller layer:
package net.codejava; @Controller public class AppController { @Autowired private UserService service; @PostMapping("/register") public String processRegister(User user) { service.registerDefaultUser(user); return "register_success"; } }
As you can see, it’s very simple – thanks to Spring Data JPA and Hibernate framework that greatly simplifies coding of the data access layer.
5. Assign Roles for User in Web Form
Now, I will show you how to code edit user functionality with web user interface in which we can change the roles assigned to a user.
Firstly, implement the following method in UserService class:
public List<User> listAll() { return userRepo.findAll(); }
And in the controller class:
@GetMapping("/users") public String listUsers(Model model) { List<User> listUsers = service.listAll(); model.addAttribute("listUsers", listUsers); return "users"; }
This handler method will show a list of users retrieved from the database. And put the following relevant code into the view page (HTML):
<table class="table table-striped table-bordered"> <thead class="thead-dark"> <tr> <th>User ID</th> <th>E-mail</th> <th>First Name</th> <th>Last Name</th> <th>Roles</th> <th></th> </tr> </thead> <tbody> <tr th:each="user: ${listUsers}"> <td th:text="${user.id}">User ID</td> <td th:text="${user.email}">E-mail</td> <td th:text="${user.firstName}">First Name</td> <td th:text="${user.lastName}">Last Name</td> <td th:text="${user.roles}">Roles</td> <td><a th:href="/@{'/users/edit/' + ${user.id}}">Edit</a></td> </tr> </tbody> </table>
It will show a list of users at the URL http://localhost.../users as shown below:
On this users listing page, we can click on an Edit hyperlink to edit a user. So code the handler method like this:
@GetMapping("/users/edit/{id}") public String editUser(@PathVariable("id") Long id, Model model) { User user = service.get(id); List<Role> listRoles = service.listRoles(); model.addAttribute("user", user); model.addAttribute("listRoles", listRoles); return "user_form"; }
And implement the following two methods in the service class:
public User get(Long id) { return userRepo.findById(id).get(); } public List<Role> listRoles() { return roleRepo.findAll(); }
And in the view layer, write the following code for the edit user form:
<form th:action="@{/users/save}" th:object="${user}" method="post" style="max-width: 600px; margin: 0 auto;"> <input type="hidden" th:field="*{id}" /> <div class="m-3"> <div class="form-group row"> <label class="col-4 col-form-label">E-mail: </label> <div class="col-8"> <input type="email" th:field="*{email}" class="form-control" required /> </div> </div> <div class="form-group row"> <label class="col-4 col-form-label">Password: </label> <div class="col-8"> <input type="password" th:field="*{password}" class="form-control" required minlength="6" maxlength="10"/> </div> </div> <div class="form-group row"> <label class="col-4 col-form-label">First Name: </label> <div class="col-8"> <input type="text" th:field="*{firstName}" class="form-control" required minlength="2" maxlength="20"/> </div> </div> <div class="form-group row"> <label class="col-4 col-form-label">Last Name: </label> <div class="col-8"> <input type="text" th:field="*{lastName}" class="form-control" required minlength="2" maxlength="20" /> </div> </div> <div class="form-group row"> <label class="col-4 col-form-label">Roles: </label> <div class="col-8"> <th:block th:each="role: ${listRoles}"> <input type="checkbox" th:field="*{roles}" th:text="${role.name}" th:value="${role.id}" class="m-2" /> </th:block> </div> </div> <div> <button type="submit" class="btn btn-primary">Update</button> </div> </div> </form>
The most important thing in this page is the code that shows a list of roles and check the roles assigned to the current user:
<th:block th:each="role: ${listRoles}"> <input type="checkbox" th:field="*{roles}" th:text="${role.name}" th:value="${role.id}" class="m-2" /> </th:block>
Then the edit user form would look like this:
The cool thing here is that Thymeleaf automatically shows the selected roles according to the roles assigned to the user. And more, you can simply check/uncheck roles here to update roles for the user.
And code the handler method for handling form submission as simple as below:
@PostMapping("/users/save") public String saveUser(User user) { service.save(user); return "redirect:/users"; }
And the relevant code at the service layer:
public void save(User user) { userRepo.save(user); }
That’s some code examples about adding roles to users in a Spring Boot web application. I hope you’ve found this written tutorial helpful.
If you prefer watching video, I’d like to recommend you watch this video to see the coding in action:
Related Spring Security Tutorials:
- Spring Security Forgot Password Tutorial
- Spring Security Limit Login Attempts Example
- Spring Security OTP Email Tutorial
- Spring Security Authentication with JPA, Hibernate and MySQL
- Spring Security Role-based Authorization Tutorial
- Spring Security Customize Login and Logout
- How to Get Logged-in User's Details with Spring Security
- Spring Security: Prevent User from Going Back to Login Page if Already logged in
- Spring Security Authentication Success Handler Examples
- Spring Security Authentication Failure Handler Examples
- Spring Security Logout Success Handler Example
- Spring Security Before Authentication Filter Examples
Other Spring Boot Tutorials:
- How to create a Spring Boot Web Application (Spring MVC with JSP/ThymeLeaf)
- Spring Boot CRUD Example with Spring MVC – Spring Data JPA – ThymeLeaf - Hibernate - MySQL
- Spring Boot Hello World RESTful Web Services Tutorial
- Spring Boot Thymeleaf Form Handling Tutorial
- Spring Data JPA Paging and Sorting Examples
- Spring Boot Error Handling Guide
- Spring Boot Logging Basics
Comments
to publish a new version of the Spring Security
Add Roles to User tutorial based on the latest
Spring Boot 3.2.x, which will make it stay relevant
for a long time.
Thank you,