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.