Keycloak with Spring Boot and Kotlin- Introduction

In this step by step guide, I will show you how to set up a Keycloak server and connect to it using Spring Boot and Kotlin.
A featured image for category: Spring

1. Introduction

Hello and welcome to the first article in a series showing how to use Keycloak with Spring Boot applications.

In this step by step guide, I will show you how to set up a Keycloak server and connect to it using Spring Boot and Kotlin. Additionally, you will learn how to secure REST endpoints with Keycloak combined with the @PreAuthorize annotation.

2. What is Keycloak?

According to the documentation, Keycloak is the Open Source Identity and Access Management solution. In simple terms, it allows us to add authentication and secure our applications with minimum effort. With Keycloak, plenty of topics, like user storage, authentication, and many many more, are available out of the box.

In addition, the keycloak-spring-boot-starter makes all the process of configuration really straightforward, thus saving our precious time.

3. Install Keycloak

As the first step in this tutorial will be the installation of the Keycloak server. There are plenty of ways to achieve that, and I highly recommend you visit the official documentation to choose the most suitable one for you.

To keep this tutorial as simple as possible, I’ve decided to deploy it as a Docker container. If you have the Docker environment already installed on your machine, all you need to do is to type the following command:

docker run -p 8090:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -eย DB_VENDOR=h2 quay.io/keycloak/keycloak:11.0.3

With this command, we create a container using the keycloak:11.0.3 image and publish it’s 8080 port to the 8180 port of the host. Additionally, we pass the KEYCLOAK_USER and KEYCLOAK_PASSWORD environment variables for the initial admin user (root).

However, the simplicity comes with its own restrictions. By default, the Keycloak server runs using the H2 database, so all the configuration will be lost after the container is terminated. To prevent that we can export a realm configuration to the JSON file (I will show you this step later).

 

Image shows two ebooks people can get for free after joining newsletter

 

3. Configure Keycloak

Once the Keycloak server is up and running, let’s head to the administration console. All we need to do is to type http://localhost:8090 in the address bar of the browser. We should see the following page:

The image shows a home page of the Keycloak server

On the next page, we should see the login form. Let’s enter the credentials for the admin account we’ve already configured:

The image shows the Keycloak login page

3.1. Create Realm

After we’ve authenticated successfully, we can create a new realm. In Keycloak, realms manage a set of users, credentials, roles, and groups. Each user belongs to and logs into some realm. Moreover, they are completely isolated environments, which introduce an additional layer of security to our application.

As the next step, let’s navigate to the Add realm button:

The image shows the location of the Create Realm button inside the Keycloak admin panel

On the next page, let’s type the name for the new realm and hit the create button:

Image shows the add Keycloak realm form

Please remember this name, we will need it to configure the Spring Boot application later.

3.2. Create Roles

Nextly, let’s create some roles for our future users. Let’s navigate to the Roles tab on the left sidebar. On the next screen, we should see the list of already existing roles. Let’s click the Add role button placed in the right part of the screen.

Image shows Keycloak realm roles list

Firstly, let’s type the ROLE_USER and click save. After that, let’s add the ROLE_ADMIN which will be a composite role associated with the previous one:

Image shows how to set up the admin role in Keycloak

Please notice two important things here:

  • A composite role combines together multiple roles. To put it simply- a user with the ROLE_ADMIN will be able to access all endpoints restricted for ROLE_USER. It helps to clean the architecture of the security a lot, however, please be careful not to introduce an additional gap to the system when using it.
  • Both roles have been prefixed with a ROLE_. Spring Security will automatically prefix hasRole checks with the same value.

3.3. Create Clients

As the next step, let’s create clients. Most often, Keycloak clients are the applications and services we would like to secure, or which are obtaining tokens to access other applications.

For the purpose of this tutorial, we will create two clients. Let’s start the one, we will use to configure our Spring Boot application:

Image shows how to set up the Keycloak client for the Spring Boot application

Please notice, that we’ve set the Access Type to bearer-only. It means, that our application will not initiate a login, it’ll just pass received tokens to verify them with Keycloak. Most of the time, you will set this type of access type for the Spring Boot backends, which don’t need to call another microservices secured by Keycloak, or to use a service account.

Nextly, let’s create the public client, which will be used to get access tokens on behalf of the user:

Image shows how to create public client in Keycloak

This time, only the Direct Access Grants flow is enabled and the Access Type is set to public.
The public access type is used for clients, which cannot keep secrets securely, like frontend applications for instance.

3.4. Create Users

As the last step for Keycloak configuration, we will create users. Let’s head to the Users page, and create a user names example_user:

Image shows how to craete a new user in Keycloak

Let’s mark him enabled and his email verified as well.

The next important thing we need to do is to set the password for the user in the Credentials tab:

Image shows how to set the password for user in Keycloak

And as the last step, let’s add the desired role to the user. Let’s select the ROLE_USER and hit the Add selected button, just like below:

Image shows how to add the role to the user in Keycloak

Please repeat the same steps for the admin user. The only difference will be the selection of the ROLE_ADMIN instead.

4. Configure Spring Boot App

4.1. Imports

As usual, we will start the configuration of our Spring Boot application with imports. Let’s head to the build.gradle.kts file and add the following dependencies:

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.keycloak:keycloak-spring-boot-starter:11.0.3")
implementation("org.springframework.boot:spring-boot-starter-security:2.4.0")

4.2. Configure Application Properties

As the next step, let’s open the /resources/application.yaml file and paste the following config:

keycloak:
  realm: example
  resource: spring-boot
  bearer-only: true
  auth-server-url: http://localhost:8090/auth

Let’s see what each of the above configs mean:

  • realm– as the name suggests, it is the name of the created realm,
  • resource– matches the name of the created client,
  • bearer-only– this flag indicates, that our service has been created as a bearer-only, by default it is set to false,
  • auth-server-url– it is the URL of our Keycloak server

4.3. Configure Spring Security

As the next step, let’s create the WebSecurityConfig class:

@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig : KeycloakWebSecurityConfigurerAdapter() {

    @Autowired
    fun configureGlobal(auth: AuthenticationManagerBuilder) {
        val keycloakAuthenticationProvider = keycloakAuthenticationProvider()
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(SimpleAuthorityMapper())
        auth.authenticationProvider(keycloakAuthenticationProvider)
    }

    @Bean
    override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
        return NullAuthenticatedSessionStrategy()
    }

    @Bean
    fun keycloakConfigResolver(): KeycloakConfigResolver {
        return KeycloakSpringBootConfigResolver()
    }

    override fun configure(http: HttpSecurity) {
        super.configure(http)
        http
            .authorizeRequests()
            .antMatchers("/api/public/**").permitAll()
            .anyRequest().fullyAuthenticated()
    }
}

I totally understand that this config might be a little overwhelming, so let’s just break it down:

@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig : KeycloakWebSecurityConfigurerAdapter()

@KeycloakConfiguration annotation provides a Keycloak based Spring security configuration. It is used with classes extending KeycloakWebSecurityConfigurerAdapter, just like in our case. The @EnableGlobalMethodSecurity with prePostEnabledย set to true enables the Spring Security global method security and @PreAuthorize annotations.

@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
    val keycloakAuthenticationProvider = keycloakAuthenticationProvider()
    keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(SimpleAuthorityMapper())
    auth.authenticationProvider(keycloakAuthenticationProvider)
}

The configureGlobal function takes the AuthenticationManagerBuilder bean as a parameter and sets the KeycloakAuthenticationProvider as the authentication provider. Additionally, it sets the SimpleAuthorityMapper as the authority mapper.

@Bean
override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
    return NullAuthenticatedSessionStrategy()
}

@Bean
fun keycloakConfigResolver(): KeycloakConfigResolver {
    return KeycloakSpringBootConfigResolver()
}

With these two functions, we declare the KeycloakSpringBootConfigResolver bean and that we do not want the session to be created, as well.

override fun configure(http: HttpSecurity) {
    super.configure(http)
    http
        .authorizeRequests()
        .antMatchers("/api/public/**").permitAll()
        .anyRequest().fullyAuthenticated()
}

Finally, we override the configure method derived from the KeycloakWebSecurityConfigurerAdapter. With this config, all requests except the /api/public/** need to be fully authenticated.

5. Create Rest Controller

With all the above being done, we can create an ExampleController class, which consists of three endpoints:

@RestController
@RequestMapping("/api")
class ExampleController {

    @GetMapping("/example")
    @PreAuthorize("hasRole('USER')")
    fun getUserInfo(): ResponseEntity<String> =
        ResponseEntity.ok("User info")

    @GetMapping("/example/admin")
    @PreAuthorize("hasRole('ADMIN')")
    fun getAdminInfo(): ResponseEntity<String> =
        ResponseEntity.ok("Admin info")

    @GetMapping("/public/example")
    fun getPublicInfo(): ResponseEntity<String> =
        ResponseEntity.ok("Public info")
}

The first two of them require the user to either have the ROLE_USER or ROLE_ADMIN assigned to him. Nevertheless, the last endpoint permits anyone, even without the token.

6. Testing with cURL

Finally, we can validate if everything is working as expected.

Let’s start by obtaining the access token:

curl --location --request POST 'http://localhost:8090/auth/realms/example/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=some_user' \
--data-urlencode 'password=password' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=public'

As the result we should get the following JSON object:

{
    "access_token":  [TOKEN],
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": [TOKEN],
    "token_type": "bearer",
    "not-before-policy": 0,
    "session_state": "7981f2e1-4e50-44d6-ac81-2ba508a59680",
    "scope": "email profile"
}

The most important part of the response for us is the JWT access token, which we will use to query our endpoints. If you would like to see, how many informations are encoded within it, I highly recommend to visit jwt.io and check it out.

To fully validate our endpoints, let’s query all of them with different tokens and see if results correspond to our assumptions:

curl --location --request GET 'http://localhost:8080/api/example' \
--header 'Authorization: Bearer [TOKEN]' 

## Result for both admin and user:
User info

curl --location --request GET 'http://localhost:8080/api/example/admin' \
--header 'Authorization: Bearer [TOKEN]' 

## Result only for admin:
Admin info

curl --location --request GET 'http://localhost:8080/api/public/example' 

## Result for any request:
Public info

7. Export Realm Configuration

In the beginning, I’ve promised to show you how to export the realm configuration. The only thing we need to do is to click the Export tab on the left sidebar and nextly the Export button on the next page:

Image shows how to export the Keycloak configuration to a JSON file.

As a result, a JSON file will be generated.

If we would like to import the configuration, all we need is to head to the Import section:

Image shows how to import the realm config from a JSON file

8. Conclusion

And that would be all for this article. We’ve covered step by step, how to set up the Keycloak server with Spring Boot and Kotlin. Moreover, we’ve had a chance to see how to use it to secure Spring Boot REST endpoints with @PreAuthorize.

As I have written in the beginning, this is the first article in a series. If you would like me to cover some special aspects of the Keycloak/Spring Boot integration, please let me know in the comments, or by the contact form.

Also, to see the fully working project, please refer to this GitHub repository.

Share this:

Related content

3 Responses

  1. This useful example would be even more valuable with some additional information and corrections.
    The docker keycloak invocation doesn’t work. It should be:
    docker run -it -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=password -e DB_VENDOR=h2 quay.io/keycloak/keycloak:12.0.2
    Also, where should the WebSecurityConfig class be placed?

    Thank you.

    1. Hi Kurt, glad to meet you. You’re right, forgot that I’ve had the DB running in the background. When it comes to the class – I put it inside the config package. Please see the point 8- there is the link to the repo of this project

  2. Hi Piotr
    Thanks again for your valuable tutorial.
    Do you have any advice about including the access token when performing a POST operation? It seems that simply adding the same header used in the GET curl examples does not seem to work with POST.
    Thank you very much for any guidance that you may provide. ๐Ÿ˜€
    Kurt

Leave a Reply

Your email address will not be published. Required fields are marked *

Newsletter
Image presents 3 ebooks with Java, Spring and Kotlin interview questions.

Never miss any important updates from the Kotlin world and get 3 ebooks!

You may opt out any time. Terms of Use and Privacy Policy