1. Overview
Spring Security 5.3 introduced a Kotlin version of their DSL (Domain-Specific Language).
In this tutorial, we’ll discuss the newly introduced Kotlin DSL and how it reduces boilerplate and lets us configure security concisely.
2. Spring Security Kotlin DSL
2.1. Configuring Security
In a Spring security application, we create a SecurityFilterChain bean to customize the default security configuration:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authz -> authz
.antMatchers("/greetings/**").hasAuthority("ROLE_ADMIN")
.antMatchers("/**").permitAll()
)
.httpBasic(basic -> {});
return http.build();
}
Above, we’re securing the /greetings path and any sub-child of it by only allowing the users who have their role configured as ADMIN. All the other endpoints are accessible to all users.
The equivalent Kotlin DSL configuration would be:
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.invoke {
authorizeRequests {
authorize("/greetings/**", hasAuthority("ROLE_ADMIN"))
authorize("/**", permitAll)
}
httpBasic {}
return http.build();
}
}
Sometimes, we might want to use different security configurations for various endpoints. This is achieved by providing multiple SecurityFilterChain beans for the endpoints:
@Order(1)
@Configuration
class AdminSecurityConfiguration {
@Bean
fun filterChainAdmin(http: HttpSecurity): SecurityFilterChain {
http.invoke {
securityMatcher("/greetings/**")
authorizeRequests {
authorize("/greetings/**", hasAuthority("ROLE_ADMIN"))
}
httpBasic {}
}
return http.build()
}
}
@Configuration
class BasicSecurityConfiguration {
@Bean
fun filterChainBasic(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/**", permitAll)
}
httpBasic {}
}
return http.build()
}
}
The first one secures the /greetings endpoint and its sub-child, whereas the other one covers all other endpoints.
To do this, we make use of @Order* with value 1 to let Spring know that AdminSecurityConfiguration needs to be applied before *BasicSecurityConfiguration.
2.2. Configuring Users
To validate the configuration, we need two users, one ordinary user with the role of USER and another user with the role ADMIN:
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin").password("password").roles("USER", "ADMIN").build();
InMemoryUserDetailsManager inMemoryUserDetailsManager
= new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(user);
inMemoryUserDetailsManager.createUser(admin);
return inMemoryUserDetailsManager;
}
We can write the same configuration with the Kotlin beans DSL:
beans {
bean {
fun user(user: String, password: String, vararg roles: String) =
User.withDefaultPasswordEncoder().username(user).password(password)
.roles(*roles).build()
InMemoryUserDetailsManager(user("user", "password", "USER"),
user("admin", "password", "USER", "ADMIN"))
}
}
2.3. Configuring Endpoints
Let us create another bean to define the REST endpoint:
bean {
router {
GET("/greetings") {
request -> request.principal().map { it.name }
.map { ServerResponse.ok().body(mapOf("greeting" to "Hello $it")) }
.orElseGet { ServerResponse.badRequest().build() }
}
}
}
In the above bean definition, we are defining the /greetings router with the HTTP GET method. Furthermore, we retrieve the principal from the request and obtain the username from it. We then return a response by greeting the logged-in user.
3. Consuming the Secured Application
The cURL command is our go-to tool when it comes to interacting with our application.
First, let’s try to request the /greetings endpoint with the ordinary user:
$ curl -v -u user:password http://localhost:8080/greetings
We get back the expected 403 Forbidden:
HTTP/1.1 403
Set-Cookie: JSESSIONID=F0CBE263219CDCEDD28DE2F0C8DE3A75; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 11 Jun 2020 09:43:44 GMT
A browser would interpret this challenge and prompt us for credentials with a simple dialog.
Now, let’s request the same resource – the /greetings endpoint– but this time with the admin user to access it:
$ curl -v -u admin:password localhost:8080/greetings
Now, the response from the server is 200 OK, along with a Cookie:
HTTP/1.1 200
Set-Cookie: JSESSIONID=D1E537C2424467AF426E494610BF950F; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 11 Jun 2020 09:49:38 GMT
From the browser, the application can be consumed normally. Indeed, the key difference is that a login page is no longer a hard requirement since all browsers support HTTP Basic Authentication and use a dialog to prompt the user for credentials.
4. Conclusion
In this tutorial, we’ve seen how we can use the Spring Security Kotlin DSL to declare the security aspects of a Spring application. Additionally, we then tested our application using cURL.
As usual, the source code for this tutorial can be found on GitHub.