You are currently viewing Role-Based Access Control (RBAC) in Spring Security: A Tutorial

Role-Based Access Control (RBAC) in Spring Security: A Tutorial

Introduction

Role-Based Access Control (RBAC) is a common method for managing user permissions based on roles assigned to users. In an RBAC system, permissions to perform operations are assigned to roles, and users are assigned to roles, which simplifies the management of individual permissions. Spring Security provides robust support for RBAC, enabling developers to secure their applications effectively. This tutorial will guide you through the process of implementing RBAC in a Spring Security application.

Prerequisites

Before starting, ensure you have the following installed:

  • JDK 8 or later
  • Maven or Gradle
  • An IDE like IntelliJ IDEA or Eclipse
  • Basic knowledge of Spring Boot and Spring Security

Step 1: Set Up Your Spring Boot Project

First, create a new Spring Boot project. You can use Spring Initializr (https://start.spring.io/) to generate the project. Select the following dependencies:

  • Spring Web
  • Spring Security
  • Spring Data JPA
  • H2 Database (or any other database of your choice)

Download the project and import it into your IDE.

Step 2: Configure the Database

Define the database configuration in src/main/resources/application.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

Step 3: Create JPA Entities

Define the user and role entities. Create a User entity in src/main/java/com/example/demo/model/User.java:

package com.example.demo.model;

import javax.persistence.*;
import java.util.Set;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;
    private boolean enabled;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles;

    // getters and setters
}

Create a Role entity in src/main/java/com/example/demo/model/Role.java:

package com.example.demo.model;

import javax.persistence.*;
import java.util.Set;

@Entity
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users;

    // getters and setters
}

Step 4: Create Repositories

Create repositories for the User and Role entities. Create UserRepository in src/main/java/com/example/demo/repository/UserRepository.java:

package com.example.demo.repository;

import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

Create RoleRepository in src/main/java/com/example/demo/repository/RoleRepository.java:

package com.example.demo.repository;

import com.example.demo.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoleRepository extends JpaRepository<Role, Long> {
    Role findByName(String name);
}

Step 5: Configure Spring Security

Create a custom user details service to load user-specific data. Create CustomUserDetailsService in src/main/java/com/example/demo/service/CustomUserDetailsService.java:

package com.example.demo.service;

import com.example.demo.model.Role;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.stream.Collectors;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        Set<GrantedAuthority> authorities = user.getRoles().stream()
            .map(role -> new SimpleGrantedAuthority(role.getName()))
            .collect(Collectors.toSet());
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
    }
}

Next, configure Spring Security in src/main/java/com/example/demo/config/SecurityConfig.java:

package com.example.demo.config;

import com.example.demo.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/").permitAll()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Step 6: Initialize the Database

Create a data initialization class to prepopulate the database with users and roles. Create DataInitializer in src/main/java/com/example/demo/DataInitializer.java:

package com.example.demo;

import com.example.demo.model.Role;
import com.example.demo.model.User;
import com.example.demo.repository.RoleRepository;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

@Component
public class DataInitializer implements CommandLineRunner {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void run(String... args) throws Exception {
        Role adminRole = new Role();
        adminRole.setName("ROLE_ADMIN");
        roleRepository.save(adminRole);

        Role userRole = new Role();
        userRole.setName("ROLE_USER");
        roleRepository.save(userRole);

        User admin = new User();
        admin.setUsername("admin");
        admin.setPassword(passwordEncoder.encode("admin"));
        admin.setEnabled(true);
        Set<Role> adminRoles = new HashSet<>();
        adminRoles.add(adminRole);
        adminRoles.add(userRole);
        admin.setRoles(adminRoles);
        userRepository.save(admin);

        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder.encode("user"));
        user.setEnabled(true);
        Set<Role> userRoles = new HashSet<>();
        userRoles.add(userRole);
        user.setRoles(userRoles);
        userRepository.save(user);
    }
}

Step 7: Create Controllers

Create a simple controller to handle requests. Create HomeController in src/main/java/com/example/demo/controller/HomeController.java:

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "home";
    }

    @GetMapping("/user")
    public String user(Model model) {
        model.addAttribute("message", "Hello User");
        return "user";
    }

    @GetMapping("/admin")
    public String admin(Model model) {
        model.addAttribute("message", "Hello Admin");
        return "admin";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

Step 8: Create Views

Create simple Thymeleaf templates for the views in src/main/resources/templates/.

home.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
</head>
<body>
    <h1>Welcome</h1>
    <a href="/user">User Page</a><br>
    <a href="/admin">Admin Page</a><br>
    <a href="/login">Login</a>
</body>
</html>

user.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>User Page</title>
</head>
<body>
    <h1 th:text="${message}"></h1>


    <a href="/">Home</a>
</body>
</html>

admin.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Admin Page</title>
</head>
<body>
    <h1 th:text="${message}"></h1>
    <a href="/">Home</a>
</body>
</html>

login.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username"/>
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password"/>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

Step 9: Run the Application

Run your Spring Boot application and navigate to http://localhost:8080. You should see the home page with links to the user and admin pages. Depending on your login credentials, you will be granted access to the respective pages:

  • Username: admin | Password: admin (access to both user and admin pages)
  • Username: user | Password: user (access to the user page only)

Conclusion

In this tutorial, you have learned how to implement Role-Based Access Control (RBAC) using Spring Security. By defining roles and assigning them to users, you can manage permissions more effectively and secure your application based on user roles. This approach provides a scalable and maintainable way to handle access control in Spring-based applications.

Leave a Reply