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
Security is a critical aspect of any modern web application. In this comprehensive guide, we'll walk through implementing robust security measures for your Spring Boot REST APIs using Kotlin. We'll cover everything from basic authentication to rate limiting, ensuring your APIs are protected against common security threats.
First, let's create a new Spring Boot project with the necessary dependencies. You can use Spring Initializr or add the following to your existing build.gradle.kts
:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("io.github.bucket4j:bucket4j-core:8.1.0")
implementation("org.springframework.boot:spring-boot-starter-validation")
}
Let's start with implementing API key authentication. We'll create a custom filter that validates API keys against our database.
@Component
class ApiKeyAuthFilter : OncePerRequestFilter() {
@Autowired
private lateinit var apiKeyService: ApiKeyService
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val apiKey = request.getHeader("X-API-Key")
if (apiKey == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "No API key provided")
return
}
if (!apiKeyService.validateApiKey(apiKey)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API key")
return
}
filterChain.doFilter(request, response)
}
}
Create a service to handle API key validation:
@Service
class ApiKeyService {
// In a real application, this would be stored in a database
private val validApiKeys = setOf(
"your-secret-api-key-1",
"your-secret-api-key-2"
)
fun validateApiKey(apiKey: String): Boolean {
return validApiKeys.contains(apiKey)
}
}
To prevent abuse of our API, we'll implement rate limiting using Bucket4j:
@Component
class RateLimitingFilter : OncePerRequestFilter() {
private val buckets = ConcurrentHashMap<String, Bucket>()
private fun resolveBucket(apiKey: String): Bucket {
return buckets.computeIfAbsent(apiKey) { _ ->
Bucket4j.builder()
.addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
.build()
}
}
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val apiKey = request.getHeader("X-API-Key")
if (apiKey != null) {
val bucket = resolveBucket(apiKey)
val probe = bucket.tryConsumeAndReturnRemaining(1)
response.addHeader("X-Rate-Limit-Remaining", probe.remainingTokens.toString())
if (!probe.isConsumed) {
response.sendError(
HttpServletResponse.SC_TOO_MANY_REQUESTS,
"Rate limit exceeded. Try again in ${probe.nanosToWaitForRefill / 1_000_000_000} seconds"
)
return
}
}
filterChain.doFilter(request, response)
}
}
Configure security headers to protect against common web vulnerabilities:
@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.headers()
.xssProtection()
.and()
.contentSecurityPolicy("default-src 'self'")
.and()
.frameOptions().deny()
.and()
.contentTypeOptions()
.and()
.and()
.csrf().disable() // For REST APIs, CSRF is typically disabled
.addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter::class.java)
.addFilterAfter(rateLimitingFilter, ApiKeyAuthFilter::class.java)
}
}
Modern REST APIs require careful security configuration. While Spring Security provides robust defaults, some need adjustment for API scenarios. For instance, [CSRF protection might need to be disabled for REST APIs](https://kotlincraft.dev/articles/why-you-should-disable-csrf-protection-for-rest-apis-in-spring-boot-and-how-to-do-it-right) in certain situations.
Implement input validation using Spring's validation framework:
data class UserRequest(
@field:NotBlank(message = "Username is required")
@field:Pattern(regexp = "^[a-zA-Z0-9]{4,}$", message = "Username must be alphanumeric and at least 4 characters")
val username: String,
@field:Email(message = "Invalid email format")
val email: String,
@field:NotBlank(message = "Password is required")
@field:Size(min = 8, message = "Password must be at least 8 characters")
val password: String
)
@RestController
@RequestMapping("/api/users")
class UserController {
@PostMapping
fun createUser(@Valid @RequestBody request: UserRequest): ResponseEntity<UserResponse> {
// Implementation
return ResponseEntity.ok(UserResponse(/* ... */))
}
}
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> {
val errors = ex.bindingResult.fieldErrors.associate {
it.field to (it.defaultMessage ?: "Invalid input")
}
return ResponseEntity.badRequest().body(errors)
}
}
Here's how to test your security implementation:
@SpringBootTest
@AutoConfigureMockMvc
class SecurityConfigTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun `should return 401 when no API key is provided`() {
mockMvc.perform(get("/api/users"))
.andExpect(status().isUnauthorized)
}
@Test
fun `should return 200 when valid API key is provided`() {
mockMvc.perform(
get("/api/users")
.header("X-API-Key", "your-secret-api-key-1")
)
.andExpect(status().isOk)
}
@Test
fun `should enforce rate limiting`() {
val apiKey = "your-secret-api-key-1"
// Make 101 requests (exceeding our 100/minute limit)
repeat(101) {
mockMvc.perform(
get("/api/users")
.header("X-API-Key", apiKey)
)
}
// The 101st request should be rate limited
mockMvc.perform(
get("/api/users")
.header("X-API-Key", apiKey)
)
.andExpect(status().isTooManyRequests)
}
}
In this guide, we've covered the essential aspects of securing your Spring Boot REST APIs using Kotlin. We implemented API key authentication, rate limiting, security headers, and input validation. Remember that security is an ongoing process, and it's important to regularly review and update your security measures.
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