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
In the modern web development landscape, building secure and efficient applications often involves communication between different domains. However, this cross-origin communication isn't straightforward due to browser security restrictions. This is where CORS (Cross-Origin Resource Sharing) comes into play. In this comprehensive guide, we'll dive deep into CORS, understand why it's necessary, and learn how to implement it correctly in a Spring Boot application using Kotlin.
Before we dive into CORS, it's crucial to understand why it exists in the first place. Browsers implement a fundamental security concept called the Same-Origin Policy. This policy restricts how a document or script loaded from one origin can interact with resources from other origins.
An origin consists of three parts:
For example, if your web application is hosted at https://myapp.com
, it can't make direct AJAX calls to https://api.myapp.com
because they have different hosts, even though they're part of the same domain family.
// This request from https://myapp.com to https://api.myapp.com would fail:
fetch('https://api.myapp.com/users')
.then(response => response.json())
.catch(error => console.error('Error:', error));
This restriction exists for good reasons. Consider this scenario:
bank.com
bank.com
using your authenticated sessionCORS is a mechanism that allows servers to specify which origins are permitted to read that information from a web browser. It extends and adds flexibility to the Same-Origin Policy.
Let's look at different ways to implement CORS in a Spring Boot application.
@Configuration
class CorsConfig {
@Bean
fun corsFilter(): CorsFilter {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration().apply {
// Allow credentials
allowCredentials = true
// Allow specific origins
setAllowedOrigins(listOf("https://trusted-frontend.com"))
// Allow specific headers
addAllowedHeader("Authorization")
addAllowedHeader("Content-Type")
addAllowedHeader("Accept")
// Allow specific methods
addAllowedMethod("GET")
addAllowedMethod("POST")
addAllowedMethod("PUT")
addAllowedMethod("DELETE")
addAllowedMethod("OPTIONS")
// How long the response to the preflight request can be cached
maxAge = 3600L
}
source.registerCorsConfiguration("/**", config)
return CorsFilter(source)
}
}
@CrossOrigin(
origins = ["https://trusted-frontend.com"],
allowedHeaders = ["Authorization", "Content-Type"],
methods = [RequestMethod.GET, RequestMethod.POST],
maxAge = 3600
)
@RestController
@RequestMapping("/api/users")
class UserController {
@GetMapping
fun getUsers(): List<User> {
// Implementation
}
}
@RestController
@RequestMapping("/api/users")
class UserController {
@CrossOrigin(origins = ["https://trusted-frontend.com"])
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): User {
// Implementation
}
}
// DON'T DO THIS IN PRODUCTION
config.addAllowedOrigin("*")
// Instead, specify exact origins
config.setAllowedOrigins(listOf(
"https://your-frontend.com",
"https://admin.your-frontend.com"
))
@Configuration
class CorsConfig {
@Bean
fun corsFilter(): CorsFilter {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration()
val allowedOrigins = listOf(
"https://prod.yourapp.com",
"https://stage.yourapp.com",
"http://localhost:3000"
)
config.apply {
allowCredentials = true
setAllowedOrigins(allowedOrigins)
addAllowedHeader("*")
addAllowedMethod("*")
}
source.registerCorsConfiguration("/**", config)
return CorsFilter(source)
}
}
@Configuration
class CorsConfig(
@Value("\${cors.allowed-origins}")
private val allowedOrigins: List<String>,
@Value("\${cors.allowed-methods}")
private val allowedMethods: List<String>
) {
@Bean
fun corsFilter(): CorsFilter {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration().apply {
allowCredentials = true
setAllowedOrigins(allowedOrigins)
setAllowedMethods(allowedMethods)
addAllowedHeader("*")
}
source.registerCorsConfiguration("/**", config)
return CorsFilter(source)
}
}
application.yml:
cors:
allowed-origins:
- https://prod.yourapp.com
- https://stage.yourapp.com
allowed-methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
@SpringBootTest
@AutoConfigureMockMvc
class CorsConfigurationTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun `should allow requests from trusted origin`() {
mockMvc.perform(options("/api/users")
.header("Origin", "https://trusted-frontend.com")
.header("Access-Control-Request-Method", "GET"))
.andExpect(status().isOk)
.andExpect(header().string("Access-Control-Allow-Origin",
"https://trusted-frontend.com"))
}
@Test
fun `should reject requests from untrusted origin`() {
mockMvc.perform(options("/api/users")
.header("Origin", "https://malicious-site.com")
.header("Access-Control-Request-Method", "GET"))
.andExpect(status().isForbidden)
}
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CorsIntegrationTest {
@LocalServerPort
private var port: Int = 0
private lateinit var restTemplate: RestTemplate
@BeforeEach
fun setup() {
restTemplate = RestTemplate()
}
@Test
fun `should handle CORS preflight request`() {
val headers = HttpHeaders()
headers.add("Origin", "https://trusted-frontend.com")
headers.add("Access-Control-Request-Method", "GET")
val request = RequestEntity<Void>(headers, HttpMethod.OPTIONS,
URI("http://localhost:$port/api/users"))
val response = restTemplate.exchange(request, String::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
assertThat(response.headers["Access-Control-Allow-Origin"])
.contains("https://trusted-frontend.com")
}
}
If you're seeing errors like:
Access to XMLHttpRequest at 'https://api.yourapp.com' from origin 'https://yourapp.com'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Check your CORS configuration:
@Bean
fun corsFilter(): CorsFilter {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration().apply {
// Make sure all necessary headers are configured
allowCredentials = true
setAllowedOrigins(listOf("https://yourapp.com"))
addAllowedHeader("*")
addAllowedMethod("*")
// Add exposed headers if needed
setExposedHeaders(listOf("Content-Disposition"))
}
source.registerCorsConfiguration("/**", config)
return CorsFilter(source)
}
If using credentials (cookies, HTTP authentication), make sure:
fetch('https://api.yourapp.com/data', {
credentials: 'include'
})
config.allowCredentials = true
// Important: Cannot use wildcard with credentials
config.setAllowedOrigins(listOf("https://specific-origin.com"))
@Component
class CorsLoggingFilter : OncePerRequestFilter() {
private val logger = LoggerFactory.getLogger(CorsLoggingFilter::class.java)
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val origin = request.getHeader("Origin")
if (origin != null) {
logger.info("CORS request from origin: $origin, method: ${request.method}")
}
filterChain.doFilter(request, response)
}
}
@Component
class CorsMetricsFilter : OncePerRequestFilter() {
@Autowired
private lateinit var meterRegistry: MeterRegistry
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val origin = request.getHeader("Origin")
if (origin != null) {
meterRegistry.counter("cors.requests",
"origin", origin,
"method", request.method
).increment()
}
filterChain.doFilter(request, response)
}
}
CORS is a crucial security feature that helps protect web applications from unauthorized cross-origin access while allowing legitimate cross-origin communication. When implementing CORS in a Spring Boot application:
By following these guidelines and understanding the underlying concepts, you can implement CORS securely and effectively in your Spring Boot applications.
Remember that CORS is just one layer of security, and it should be used in conjunction with other security measures like CSRF protection, secure session management, and proper authentication/authorization mechanisms.
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