0% found this document useful (0 votes)
44 views

02 Build A Role Based Access Control in A Spring Boot 3 API

This document discusses implementing role-based access control (RBAC) in a Spring Boot application. It begins by describing the different user roles - User, Administrator, and Super Administrator - and the protected routes that require each role. It then covers setting up the project, creating Role and User entities with a one-to-one relationship, seeding predefined roles into the database on startup, and updating user creation to assign the default User role. The implementation enhances the security of the application by restricting access to routes and data based on a user's role.

Uploaded by

Wowebook Pro
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
44 views

02 Build A Role Based Access Control in A Spring Boot 3 API

This document discusses implementing role-based access control (RBAC) in a Spring Boot application. It begins by describing the different user roles - User, Administrator, and Super Administrator - and the protected routes that require each role. It then covers setting up the project, creating Role and User entities with a one-to-one relationship, seeding predefined roles into the database on startup, and updating user creation to assign the default User role. The implementation enhances the security of the application by restricting access to routes and data based on a user's role.

Uploaded by

Wowebook Pro
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 23

Spring Boot

Implement Role-based
Control in Spring Boot 3Access
Eric Cabrel TIOGO
Oct 15, 2023 • 10 min read

Photo by fabio / Unsplash


Design and Development
tips in your inbox. Every
weekday.
ADS VIA CARBON

Role-based Access Control (RBAC) is a valuable access control model


that enhances security, simplifies access management, and improves
efficiency. It is particularly beneficial in complex environments where
managing access to resources is critical to security and operations.
This tutorial is the suite of this tutorial where we implemented a JWT
authentication in Spring Boot 3 and Spring Security.

JWT authentication in Spring Boot 3 with Spring


Security 6
Learn how to enhance the security of your Spring Boot 3
application by implementing JSON Web Token (JWT)…
Teco Tutorials • Eric Cabrel TIOGO

What we will do
We have a Web API that has public routes and restricted routes. The
restricted routes require a valid JWT from a user in the database.
Now that the user is authenticated, we want to go further by allowing
access to some data only if the user has a specific role.
Here are the following roles we have in our system:
User: can access his information
Administrator: can do everything the User role does and
access the users' list.
Super Administrator: can do everything the Admin role does
and create an admin user; shortly, he can do everything.
The table below lists the protected routes with the role required to
access them.
API ROUTE ROLE REQUIRED DESCRIPTION

[GET] /users/me User, Admin, Super Admin Retrieve the authenticated user

[GET] /users Admin, Super Admin Retrieve the list of all users

[POST] /admins Super Admin Create an administrator

Prerequisites
To follow this tutorial, make sure you have the following tools installed
on your computer.
JDK 11 or higher - Download link
Maven 3.8 or higher - Download link
Docker - Download link
Has followed this tutorial about JWT authentication in Spring
Boot.
We need Docker to run a container for MySQL 8; you can skip it if
MySQL is installed on your computer. Run the command below to start
the Docker container from the MySQL image:
docker run -d -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=taskdb --name

Set up the project


We will start from the final project of the previous tutorial since it is the
continuity. The project is in the blog repository GitHub.
We will do a sparse checkout to only clone the project folder we want in
the Git repository.
Run the commands below to configure the project locally and launch it:

git clone --no-checkout https://github.com/tericcabrel/blog-tutorials.git

cd blog-tutorials

git sparse-checkout init --cone

git sparse-checkout set springboot-jwt-auth

git checkout @

cd springboot-jwt-auth

mvn install -DskipTests

mvn spring-boot:run

Before running the last command, ensure the MySQL Docker container
is running. The application will start on port 8005.
The Spring Boot application runs on port 8005.

Let's register a user with the following cURL request.

curl -XPOST -H "Content-type: application/json" -d '{


"email": "[email protected]",
"password": "123456",
"fullName": "Jon Snow"
}' 'http://localhost:8005/auth/signup'

We get the following output.


Register a new user.

Let's authenticate with the user created previously using the following
cURL request.

curl -XPOST -H "Content-type: application/json" -d '{


"email": "[email protected]",
"password": "123456"
}' 'http://localhost:8005/auth/login'

We get the following output.


Authenticate a user.

We can see everything works as expected; let's continue!

Create the role entity


The role entity will represent the different roles needed in our system.
We will create an enum to represent all the possible role names.
In the package "entities", create a file "RoleEnum.java" and add the
code below:

package com.tericcabrel.authapi.entities;

public enum RoleEnum {


USER,
ADMIN,
SUPER_ADMIN
}
In the package "entities", create a file "Role.java" and add the code
below:

package com.tericcabrel.authapi.entities;

import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.util.Date;

@Table(name = "roles")
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(nullable = false)
private Integer id;
@Column(unique = true, nullable = false)
@Enumerated(EnumType.STRING)
private RoleEnum name;
@Column(nullable = false)
private String description;
@CreationTimestamp
@Column(updatable = false, name = "created_at")
private Date createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private Date updatedAt;
// Getters and setters here....
}
Create the "RoleRepository.java" which represents the Data Access
Layer for the Role entity.

package com.tericcabrel.authapi.repositories;

import com.tericcabrel.authapi.entities.Role;
import com.tericcabrel.authapi.entities.RoleEnum;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface RoleRepository extends CrudRepository<Role, Integer> {
Optional<Role> findByName(RoleEnum name);
}

Re-run the application and ensure the roles table has been created in
the database.
Store the pre-defined roles in the database
We already know roles to persist in the system. So, before creating a
user, we must ensure the role exists in the database.
We will create a function executed at the application startup to create
roles in the database if they don't exist.
Spring Boot allows executing some actions on the application startup;
we will use it here, so let's create a package called "bootstrap", then
create the file "RoleSeeder.java" and add the code below:
package com.tericcabrel.authapi.bootstrap;

import com.tericcabrel.authapi.entities.Role;
import com.tericcabrel.authapi.entities.RoleEnum;
import com.tericcabrel.authapi.repositories.RoleRepository;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class RoleSeeder implements ApplicationListener<ContextRefreshedEvent> {
private final RoleRepository roleRepository;

public RoleSeeder(RoleRepository roleRepository) {


this.roleRepository = roleRepository;
}

@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
this.loadRoles();
}

private void loadRoles() {


RoleEnum[] roleNames = new
RoleEnum[] { RoleEnum.USER, RoleEnum.ADM
Map<RoleEnum, String> roleDescriptionMap = Map.of(
RoleEnum.USER, "Default user role",
RoleEnum.ADMIN, "Administrator role",
RoleEnum.SUPER_ADMIN, "Super Administrator role"
);

Arrays.stream(roleNames).forEach((roleName) -> {
Optional<Role> optionalRole = roleRepository.findByName(roleName

optionalRole.ifPresentOrElse(System.out::println, () -> {
Role roleToCreate = new Role();

roleToCreate.setName(roleName)
.setDescription(roleDescriptionMap.get(roleName));
roleRepository.save(roleToCreate);
});
});
}
}

Re-run the application and verify the roles have been created in the
database.

Web Application roles are saved in the database.

Update the User entity to include the role


We must associate a role for each user to use it later to determine
whether it can access a resource. We can achieve this by creating a
one-to-one relationship between the user and the role.

Many-To-Many relationship with JPA and Spring Boot -


part 1
In this post, we will see how to create many-to-many
relationships between entities using JPA and Hibernate ins a…
Teco Tutorials • Eric Cabrel TIOGO
Let's update the file "User.java" in the package "entities" to add the
code below:

@OneToOne(cascade = CascadeType.REMOVE)
@JoinColumn(name = "role_id", referencedColumnName = "id", nullable = false)
private Role role;
public Role getRole() {
return role;
}

public User setRole(Role role) {


this.role = role;
return this;
}

Re-run the application and ensure the users' table has been updated.

The table "users" has the role_id column.

💡 Make sure to empty the user table; otherwise, the column addition will fail
because the "role_id" column is not nullable.
Set the role when creating a user
The user role is now required, so creating a user without one will throw
an error. We must update signup() function in the
AuthenticationService.java; replace the function with the code below:

public User signup(RegisterUserDto input) {


Optional<Role> optionalRole = roleRepository.findByName(RoleEnum.USER);

if (optionalRole.isEmpty()) {
return null;
}

var user = newUser()


.setFullName(input.getFullName())
.setEmail(input.getEmail())
.setPassword(passwordEncoder.encode(input.getPassword()))
.setRole(optionalRole.get());

return userRepository.save(user);
}

We inject the "RoleRepository" as a dependency of the


AuthenticationService class.

💡 We don't throw a runtime exception when the role is not found for brevity.

Re-run the application and try to register a user; we can see that the
user role is returned in the response.
Registering a user returns his role in the API response.

Access the user role in the authentication context


In the user entity class (User.java), the function getAuthorities()
returns all the authorities associated with this user; it was empty by
default, but now we must update it to produce a list containing the
user's role name.
Replace the function getAuthorities() with the code below:

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority( "ROLE_" +

return List.of(authority);
}

This allows us to access the user role from the authentication context.

💡 For role-based authorization, Spring Security adds a default ROLE_ prefix to


the value given. This is why we concatenate the role's name with "ROLE_".

Enable the method security of Spring Security


To restrict user access based on their roles, we must enable the feature
in Spring security, allowing us to perform the check without writing a
custom logic.
You must add the annotation @EnableMethodSecurity on the security
configuration file "SecurityConfiguration.java".

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
// The existing code configuration here...
}

Adding this annotation gives us annotations we can use at the


controller level to perform role access control.

Protect the API routes for user and admin role


Open the file UserController.java and add the annotation for the
following routes:
"/users/me": @PreAuthorize("isAuthenticated()")
"/users": @PreAuthorize("hasAnyRole('ADMIN',
'SUPER_ADMIN')")
Since the route "users/me" is accessible by all the roles, we can just
check that the user is authenticated.
package com.tericcabrel.authapi.controllers;

import org.springframework.security.access.prepost.PreAuthorize;

@RequestMapping("/users")
@RestController
public class UserController {
private final UserService userService;

public UserController(UserService userService) {


this.userService = userService;
}

@GetMapping("/me")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<User> authenticatedUser() {
// Existing code here...
}

@GetMapping
@PreAuthorize("hasAnyRole('ADMIN', 'SUPER_ADMIN')")
public ResponseEntity<List<User>> allUsers() {
// Existing code here...
}
}
That's it! Spring Security will handle the rest under the hood.

Protect the API route for the super admin role


The endpoint "/admins" creates a new admin in the system and is
accessible only by a user having a super admin role.
Since the implementation doesn't exist, let's add it in the
UserService.java:

package com.tericcabrel.authapi.services;

import com.tericcabrel.authapi.dtos.RegisterUserDto;
import com.tericcabrel.authapi.entities.Role;
import com.tericcabrel.authapi.entities.RoleEnum;
import com.tericcabrel.authapi.entities.User;
import com.tericcabrel.authapi.repositories.RoleRepository;
import com.tericcabrel.authapi.repositories.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class UserService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;

private final PasswordEncoder passwordEncoder;

public UserService(UserRepository userRepository, RoleRepository roleRepository, Passwo


this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.passwordEncoder = passwordEncoder;
}
public List<User> allUsers() {
List<User> users = new ArrayList<>();

userRepository.findAll().forEach(users::add);

return users;
}

public User createAdministrator(RegisterUserDto input) {


Optional<Role> optionalRole = roleRepository.findByName(RoleEnum.ADM

if (optionalRole.isEmpty()) {
return null;
}

var user = newUser()


.setFullName(input.getFullName())
.setEmail(input.getEmail())
.setPassword(passwordEncoder.encode(input.getPassword()))
.setRole(optionalRole.get());

return userRepository.save(user);
}
}

In the package "controllers", create a new file "AdminController.java"


and add the code below:

package com.tericcabrel.authapi.controllers;

import com.tericcabrel.authapi.dtos.RegisterUserDto;
import com.tericcabrel.authapi.entities.User;
import com.tericcabrel.authapi.services.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/admins")
@RestController
public class AdminController {
private final UserService userService;

public AdminController(UserService userService) {


this.userService = userService;
}

@PostMapping
@PreAuthorize("hasRole('SUPER_ADMIN')")
public ResponseEntity<User> createAdministrator(@RequestBody RegisterUserDto registe
User createdAdmin = userService.createAdministrator(registerUserDto

return ResponseEntity.ok(createdAdmin);
}
}

We use the function hasRole() to enforce that the authenticated user


must have the super admin role.
There is no endpoint to create a super admin, so we must create one at
the application startup. In the package "bootstrap", create a file
"AdminSeeder.java" and add the code below:

package com.tericcabrel.authapi.bootstrap;

import com.tericcabrel.authapi.dtos.RegisterUserDto;
import com.tericcabrel.authapi.entities.Role;
import com.tericcabrel.authapi.entities.RoleEnum;
import com.tericcabrel.authapi.entities.User;
import com.tericcabrel.authapi.repositories.RoleRepository;
import com.tericcabrel.authapi.repositories.UserRepository;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class AdminSeeder implements ApplicationListener<ContextRefreshedEvent> {
private final RoleRepository roleRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

public AdminSeeder(
RoleRepository roleRepository,
UserRepository userRepository,
PasswordEncoder passwordEncoder
){
this.roleRepository = roleRepository;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}

@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
this.createSuperAdministrator();
}

private void createSuperAdministrator() {


RegisterUserDto userDto = new RegisterUserDto();
userDto.setFullName("Super Admin").setEmail("[email protected]").set

Optional<Role> optionalRole = roleRepository.findByName(RoleEnum.SU


Optional<User> optionalUser = userRepository.findByEmail(userDto.get

if (optionalRole.isEmpty() || optionalUser.isPresent()) {
return;
}

var user = new


User()
.setFullName(userDto.getFullName())
.setEmail(userDto.getEmail())
.setPassword(passwordEncoder.encode(userDto.getPassword()))
.setRole(optionalRole.get());
userRepository.save(user);
}
}

Re-run the application and test the implementation:

Calling the API routes with super admin user.

Wrap up
In this post, we saw how to implement a Role Based Access Control in a
Spring Boot application, and here are the main steps to remember:
Create the role entity and data access layer.
Associate the user entity with a role.
Expose the user's role in the authentication context.
Enable the method security Spring security.
Protect the API route using the method security route
isAuthenticated() , hasRole() and hasAnyRole() .

To learn more about the method security of Spring Security and other
methods, you can such as hasAuthority() , check out the
documentation link.
You can find the code source on the GitHub repository.
Follow me on Twitter or subscribe to my newsletter to avoid missing the
upcoming posts and the tips and tricks I occasionally share.

Join the Newsletter


Get updates about new articles, contents, coding tips and tricks.
Email Address Subscribe

No spam, no ads, no sales. Unsubscribe at any time.


Spring Boot 3 and Docker Implement JWT
compose integration authentication in a Spring
This post shows how to take advantage of Boot 3 application
the Spring Boot integration Docker… Learn how to enhance the security of your
Oct 20, 2023 6 min read Spring Boot 3 application by implementin…
Oct 5, 2023 16 min read

Teco Tutorials © 2023


Twitter GitHub LinkedIn
Powered by Ghost

You might also like