Configure Roles in Spring Security





Hey guys in this post, we will discuss configuring roles in the Spring security application. This is the continuation of the previous post, please follow that post before proceeding with this.

Complete example


There are situations, where we need to configure the roles in our application. Spring Security has provided a feature to authorize the users based on their roles.

Let’s create a step-by-step spring boot project and customize the Authentication provider.

Create database and tables


In order to create our own custom implementation of UserDetailsService, first we need to create database tables for our users. To create a database and tables execute the following query

CREATE database springsecurity;

USE springsecurity;

CREATE TABLE tbl_employees
(
	id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    pwd VARCHAR(255) NOT NULL,
    role VARCHAR(255) NOT NULL
);

INSERT INTO tbl_employees VALUES (NULL, "[email protected]", "$2y$12$ZYU8aMJQBkxCF0DK9zLfnuTRP3RLVEI6PS/wbdESPth9MwCmSDRv.", "ROLE_ADMIN");
INSERT INTO tbl_employees VALUES (NULL, "[email protected]", "$2y$12$.mT/8vOVrDcAJ0r.h/WiHuSuCmKhV5P6Z5QAhIZ6c5AdCRHPXe66u", "ROLE_USER");

Remember when we are working with Roles in spring security, we prefix all the roles with keyword ROLE followed by the name. Ex: ROLE_ADMIN, ROLE_ROOT, ROLE_USER

So in this example, we are not discussing creating a new user, so in order to hash the password, you can visit the website https://bcrypt-generator.com/ to hash the password.
Screenshot-2021-06-01-at-3-25-46-PM

Create spring boot project


There are many different ways to create a spring boot application, you can follow the below articles to create one –

>> Create spring boot application using Spring initializer
>> Create spring boot application in Spring tool suite [STS]
>> Create spring boot application in IntelliJ IDEA

Add maven dependencies


Open pom.xml and add the following dependencies –

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.0</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>in.bushansirgur</groupId>
	<artifactId>springsecurityproject</artifactId>
	<version>v1</version>
	<name>springsecurityproject</name>
	<description>Spring security project</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

spring-boot-starter-web dependency for building web applications using Spring MVC. It uses the tomcat as the default embedded container. spring-boot-starter-security dependency, which will help to implement spring security. mysql-connector-java dependency for connecting to the MySQL database. spring-boot-starter-data-jpadependency to interact with the database and perform the database operations.

Configure datasource


Open application.properties file and add the following contents –

spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity
spring.datasource.username=scbushan05
spring.datasource.password=scbushan05

Create a Rest controller


Create HomeController.java inside the in.bushansirgur.springboot.controller package and add the following content

package in.bushansirgur.springboot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {
	
	@RequestMapping("/home")
	public String showHomePage () {
		return "displaying the home page contents";
	}
	
	@RequestMapping("/protected")
	public String commonPage () {
		return "displying the commong page contents";
	}
	
	@RequestMapping("/user")
	public String userPage () {
		return "displying the user page contents";
	}
	
	@RequestMapping("/admin")
	public String adminPage () {
		return "displying the admin page contents";
	}
}

We have created two handler methods showHomePage(), which is mapped to /home, anyone can access this URI and commonPage(), which is mapped to /protected, only authorized users and having roles ADMIN and USER can access this URI. Next userPage() is mapped to /user, only authorized users and having role USER can access this URI. Similarly, adminPage(), which is mapped to /admin, only authorized users and having role ADMIN can access this URI.




Create an entity class


Create an entity class Employee.java inside the in.bushansirgur.springboot.entity package and add the following content

package in.bushansirgur.springboot.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity(name =  "tbl_employees")
public class Employee {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	private String email;
	
	private String pwd;
	
	private String role;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPwd() {
		return pwd;
	}

	public void setPwd(String pwd) {
		this.pwd = pwd;
	}

	public String getRole() {
		return role;
	}

	public void setRole(String role) {
		this.role = role;
	}
}

This is just a POJO with private fields, setters, getters, and JPA annotations. The class is annotated with @Entity annotation, this represents that this class is mapped with a database table.

Create a Repository


Create an interface EmployeeRepository.java inside the in.bushansirgur.springboot.repository package and add the following content

package in.bushansirgur.springboot.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import in.bushansirgur.springboot.entity.Employee;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
	
	List<Employee> findByEmail(String email);
}

Here we are creating a query method findByEmail() to retrieve the list of employees.

Create a service


Next, we need to create a service class that implements the UserDetails. Create a class EmployeeService.java inside the in.bushansirgur.springboot.service package and add the following content

package in.bushansirgur.springboot.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import in.bushansirgur.springboot.entity.Employee;

public class EmployeeService implements UserDetails {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private final Employee employee;
	
	public EmployeeService (Employee employee) {
		this.employee = employee;
	}
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> authorities = new ArrayList<>();
		authorities.add(new SimpleGrantedAuthority(employee.getRole()));
		return authorities;
	}

	@Override
	public String getPassword() {
		return employee.getPwd();
	}

	@Override
	public String getUsername() {
		return employee.getEmail();
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}

Here we create a constructor and pass the Employee object. So in the future whoever, creating  EmployeeService they need to pass the Employee object as well. With that Employee object, we will retrieve the username, password and authorities.




Create a custom authentication provider


Create a class CustomAuthenticationProvider inside the in.bushansirgur.springboot.config package that implements AuthenticationProvider. We will mark this class with @Component annotation so that as soon as the application loads spring will detect this class. Open the file and add the following content

package in.bushansirgur.springboot.config;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import in.bushansirgur.springboot.entity.Employee;
import in.bushansirgur.springboot.repository.EmployeeRepository;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider{
	
	@Autowired
	private EmployeeRepository eRepository;
	
	@Autowired
	private PasswordEncoder pEncoder;

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		String username = authentication.getName();
		String password = authentication.getCredentials().toString();
		List<Employee> employee = eRepository.findByEmail(username);
		if (employee.size() > 0) {
			if (pEncoder.matches(password, employee.get(0).getPwd())) {
				List<GrantedAuthority> authorities = new ArrayList<>();
				authorities.add(new SimpleGrantedAuthority(employee.get(0).getRole()));
				return new UsernamePasswordAuthenticationToken(username, password, authorities);
			}else  {
				throw new BadCredentialsException("Invalid password");
			}
		} else {
			throw new BadCredentialsException("No user registered with this details");
		}
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return authentication.equals(UsernamePasswordAuthenticationToken.class);
	}
}

Anytime if we want to create our own custom implementation for AuthenticationProvider then we need to implement the AuthenticationProvider which will override two methods, one is authenticate (Authentication auth) which receives the Authentication object and the second is supports(). Inside these two methods, we will write our business logic to authenticate the user.

Create a configuration class


Let’s customize the spring security to deny all the URIs. Create ProjectSecurityConfig.java inside the in.bushansirgur.springboot.config package and add the following content.

package in.bushansirgur.springboot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
		.antMatchers("/home").permitAll()
		.antMatchers("/protected").hasAnyRole("ADMIN", "USER")
		.antMatchers("/admin").hasRole("ADMIN")
		.antMatchers("/user").hasRole("USER")
		.and()
		.formLogin()
		.and()
		.httpBasic();
	}
	
	@Bean
	public PasswordEncoder passwordEncoder () {
		return new BCryptPasswordEncoder();
	}
}

Remember, for the hasRole("ADMIN") and hasAnyRole("ADMIN", "USER") we will not prefix the ROLE, by default Spring security will add the ROLE for us. This is the default behavior of the spring security framework.

  • hasRole(): Accepts a single role name for which the endpoint will be configured and user will be validated against the single role
    mentioned. Only users having the same role configured can call the endpoint.
  • hasAnyRole(): Accepts multiple roles for which the endpoint will be configured and user will be validated against the roles
    mentioned. Only users having any of the role configured can call the endpoint.
  • access(): Using Spring Expression Language (SpEL) it provides you unlimited possibilities for configuring roles which are not
    possible with the above methods. We can use operators like OR, AND inside access() method.

Run the app


Run the application using the below maven command –

mvn spring-boot:run

Open the browser and enter the following URL –
http://localhost:8080/home
Screenshot-2021-05-05-at-8-16-42-PM
http://localhost:8080/protected
Screenshot-2021-05-05-at-8-16-16-PM

Enter the username and password, this will allow the user to access the contents because /protected can be accessible by both roles ROLE_USER and ROLE_ADMIN.

http://localhost:8080/admin
Screenshot-2021-06-05-at-11-30-46-AM
Now if we enter credentials for ROLE_USER, we do get 403 forbidden
Screenshot-2021-06-05-at-11-31-17-AM

http://localhost:8080/user
Screenshot-2021-06-05-at-11-38-34-AM
Now if we enter credentials for ROLE_ADMIN, we do get 403 forbidden
Screenshot-2021-06-05-at-11-38-48-AM

That’s it for this post, if you like this post, share this with your friends and colleagues or you can share this within your social media platform. Thanks, I will see you in our next post.



Bushan Sirgur

Hey guys, I am Bushan Sirgur from Banglore, India. Currently, I am working as an Associate project in an IT company.

Leave a Reply