Role-Based Access Control (RBAC) in Spring Security with Kotlin
Master Role-Based Access Control (RBAC) in Spring Boot applications using Kotlin with practical examples, from basic setup to advanced configurations with method-level security
Role-Based Access Control (RBAC) is a crucial security mechanism that restricts system access based on the roles of individual users. In this comprehensive guide, we'll implement RBAC in a Spring Boot application using Kotlin, covering everything from basic setup to advanced configurations.
When building enterprise applications, you often need different levels of access for different types of users. For example:
RBAC provides a clean, scalable way to manage these permissions. We'll build a complete example showing how to implement this in Spring Boot with Kotlin.
To follow this tutorial, you'll need:
First, create a new Spring Boot project with the following dependencies in your build.gradle.kts
:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
// For demonstration purposes, we'll use H2 database
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
First, let's define our roles:
enum class Role {
ROLE_USER,
ROLE_MANAGER,
ROLE_ADMIN
}
Next, we'll create our user entity with role support:
@Entity
@Table(name = "users")
class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(unique = true)
val username: String,
val password: String,
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "user_roles", joinColumns = [JoinColumn(name = "user_id")])
@Enumerated(EnumType.STRING)
val roles: Set<Role> = setOf(Role.ROLE_USER)
)
Create a service to load user details:
@Service
class UserDetailsServiceImpl(
private val userRepository: UserRepository
) : UserDetailsService {
override fun loadUserByUsername(username: String): UserDetails {
val user = userRepository.findByUsername(username)
?: throw UsernameNotFoundException("User not found")
return org.springframework.security.core.userdetails.User
.withUsername(user.username)
.password(user.password)
.roles(*user.roles.map { it.name.removePrefix("ROLE_") }.toTypedArray())
.build()
}
}
Set up the security configuration:
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val userDetailsService: UserDetailsServiceImpl
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { it.disable() }
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/manager/**").hasRole("MANAGER")
.requestMatchers("/api/user/**").hasRole("USER")
.anyRequest().authenticated()
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.userDetailsService(userDetailsService)
return http.build()
}
@Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
}
Let's create some endpoints with different role requirements:
@RestController
@RequestMapping("/api")
class UserController {
@GetMapping("/public/info")
fun getPublicInfo() = "This is public information"
@GetMapping("/user/profile")
@PreAuthorize("hasRole('USER')")
fun getUserProfile(authentication: Authentication): String {
return "User profile for: ${authentication.name}"
}
@GetMapping("/manager/reports")
@PreAuthorize("hasRole('MANAGER')")
fun getManagerReports(): String {
return "Manager reports"
}
@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')")
fun getAdminUserList(): String {
return "Admin user list"
}
}
Spring Security also supports method-level security. Here's how to implement it:
@Configuration
@EnableMethodSecurity
class MethodSecurityConfig
@Service
class UserService {
@PreAuthorize("hasRole('ADMIN')")
fun deleteUser(userId: Long) {
// Delete user logic
}
@PreAuthorize("hasRole('MANAGER') or @userSecurity.isOwner(#userId)")
fun updateUser(userId: Long, userDetails: UserDetails) {
// Update user logic
}
}
You can create custom security expressions for complex authorization rules:
@Component("userSecurity")
class UserSecurityEvaluator(
private val userRepository: UserRepository
) {
fun isOwner(userId: Long): Boolean {
val authentication = SecurityContextHolder.getContext().authentication
val username = authentication.name
return userRepository.findByIdAndUsername(userId, username) != null
}
}
Here's how to test your RBAC implementation:
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
@WithMockUser(roles = ["ADMIN"])
fun `admin can access admin endpoints`() {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk)
}
@Test
@WithMockUser(roles = ["USER"])
fun `user cannot access admin endpoints`() {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden)
}
}
@Bean
fun roleHierarchy(): RoleHierarchy {
val hierarchy = RoleHierarchyImpl()
hierarchy.setHierarchy("""
ROLE_ADMIN > ROLE_MANAGER
ROLE_MANAGER > ROLE_USER
""".trimIndent())
return hierarchy
}
@EnableJpaAuditing
class AuditConfig {
@Bean
fun auditorAware(): AuditorAware<String> {
return AuditorAware {
Optional.ofNullable(SecurityContextHolder.getContext().authentication?.name)
}
}
}
ROLE_
prefix in database but remove it when using hasRole()
RBAC is a powerful way to manage access control in your Spring Boot applications. By following this guide, you've learned how to:
Role-Based Access Control is commonly used alongside JWT authentication. If you haven't set up authentication yet, check out our guide on [implementing JWT authentication in Spring Boot](https://kotlincraft.dev/articles/jwt-authentication-in-spring-boot-with-kotlin-from-zero-to-production). For API-only applications, you might also want to review our guide on [CSRF protection in REST APIs](https://kotlincraft.dev/articles/why-you-should-disable-csrf-protection-for-rest-apis-in-spring-boot-and-how-to-do-it-right) to ensure your security configuration is optimized for your use case.
Master Role-Based Access Control (RBAC) in Spring Boot applications using Kotlin with practical examples, from basic setup to advanced configurations with method-level security
Learn how to implement secure API key authentication in Spring Boot with Kotlin, including rate limiting, monitoring, and production-ready best practices - complete with practical examples and ready-to-use code
Learn why CSRF protection isn't necessary for most REST APIs and how to properly disable it in Spring Boot while maintaining security. Includes code examples and best practices