Securing applications and data within them has always been of great significance. Running into some form of identity and access control while developing applications is unavoidable, and implementing full user and role management required for authentication and authorization might not be the application’s focus.  In another scenario, one of the requirements might be that an external team is handling identity and access management, which should be separated from the main application. For such cases, Identity Access Management tools are very useful.

Identity Access Management (IAM) tools are systems designed for managing users and access policies. Their core goal is ensuring that users are authenticated and authorized to access resources in the system based on their role. Therefore, they include a database of user identities and offer features for full access privilege management along with monitoring and logging systems in place. Of course, IAM systems provide many more features such as, Single-Sign-On (SSO), various types of authentication (two-factor, multifactor), social logins, etc.

It is no surprise that a vast amount of such solutions, both paid and free, are available online, with popular choices being OneLogin and Auth0. However, the focus of our topic will be a tool named Keycloak.

What is Keycloak?

To start with, Keycloak is an open-source identity and access management solution developed and maintained by RedHat. As they say, it is a solution aimed at modern applications and services and makes securing them an easy endeavor with little to no code. 

Keycloak offers centralized management with both administrator and account management consoles available. The administrator console enables access to full Keycloak configuration, while the account management console enables users to manage their profile and active sessions. Even though it uses its own database of user identities, user federation is one of Keycloak’s features, and it provides support for integration with existing LDAP or Active Directory servers. It is also worth mentioning that the database can be configured. It also fully supports SSO features, customization of authentication flows, and many other features. Based on standard protocols, it supports OpenID Connect (OIDC, based on OAuth2.0), OAuth2.0, and SAML 2.0, with OIDC being recommended. It is widely used and well documented, with tremendous community support. The capabilities of Keycloak are way broader than we have space to discuss in this article, having most of the key features mentioned. More details can be found in the official documentation.

Now that we know what Keycloak offers, how do you integrate it with your application? For the remainder of this article, we will set up the basic configuration of the Keycloak server and provide an example of securing REST API using Keycloak and Spring Security.

Getting started with Keycloak

Keycloak can be set up using a container image, operator, or distribution file downloaded from the official website. At the moment of writing, the latest release is 20.0.1, and we will use a standalone distribution. Keycloak server is ready to use immediately, and it can be started by running the following command in the bin folder:

./kc.sh start-dev (MacOS)
./kc.bat start-dev (Windows)

This command will start the server in development mode on default port 8080. 

When accessing Keycloak for the first time, you will be prompted to create an initial admin user. Given that you created the user and logged in, you should have access to the Admin Console shown on Figure 1. 

Securing Spring Boot Applications with Keycloak and Spring SecurityFigure 1. Keycloak Admin Console

Looking at Figure 1., on the left-hand side, we can see that we are currently in the master realm. A realm within Keycloak is a domain where we apply our security policies. All realms are isolated and have their own configurations, users, roles, policies, etc. A user belongs to and logs into a realm. Master is the default and parent realm to all user-created realms in Keycloak, and should never be used as the main realm. 

Creating realm roles 

Creating a new realm with default configurations is a simple task. From the dropdown menu in the top left corner (Figure 1), click Create Realm, and you will be prompted with a form. Optionally, we can import a realm from the existing JSON file. More on exporting and importing realms and users can be found hereAfter the realm, we move to creating roles. The demo application for this article will only have two roles: ADMIN and USER. Select Realm roles from the menu on the left to access realm role management. It is worth mentioning here that we are creating roles on realm level, for all applications. Separate roles can be created for different applications (clients), but in our case, the realm level will suffice for a simple demonstration.  Creating a new role is pretty straightforward.

Even though we only have two roles in this example, we want the ADMIN role to have both admin and regular user privileges, which will be achieved by making the ADMIN role composite. We create two users: admin and mirza.m

To create a new user, from the left-hand side menu visible in Figure 1., use the menu option Users, then hit the button for creating a new User. An example of a filled form for a user is shown in Figure 2.

Securing Spring Boot Applications with Keycloak and Spring SecurityFigure 2. Creating a new user in Admin Console

The required user actions dropdown allows us to define actions the user must take upon the first login. We will require no actions for users upon first login. It’s important to know that by default, Keycloak realms have no defined password policy, which we also will not enforce.

By default, new users do not have any credentials set, so starting credentials and appropriate roles should be set up after creating users. Set credentials to a user via the Credentials tab, then navigate to the Role mapping tab and click the Assign roles button. You will be prompted with a modal dialog where you can assign roles to the user, as shown in Figure 3. 

Adding a default user role to newly created users would be wise. We can do so by navigating to Realm settings from the menu on the left-hand side and then selecting the User Registration tab. Follow the same process as with assigning roles.

Securing Spring Boot Applications with Keycloak and Spring SecurityFigure 3. Assigning roles to User in Admin Console

A full Keycloak setup could be done through Admin CLI or bootstrapped along with the application through Admin API.

Securing Spring Boot REST API using Keycloak and Spring Security

In this section, we will create a demo Spring Boot Application with a simple RESTful Web Service, which we will later secure using Keycloak and Spring Security. You can quickly generate a spring boot project using Spring Initialzr. We will use Spring Boot 3, and Maven with the following dependencies: 

  • Spring Web
  • Spring Data JPA 
  • H2 – Embedded, in-memory database 
  • Lombok – Utilize code generation using of annotations and avoid boilerplate code
  • Apache Commons Collections

The main resources that the web service will provide are Articles, defined in Article.java class. We will have three HTTP endpoints defined:

  • GET /articles/all – Gets the list of all articles. Available to authenticated users with a USER role
  • GET /articles/minified – Gets the list of all articles with minified content. Publicly available, unauthenticated users cannot see full articles
  • POST /articles/create – Creates a new Article. Available to authenticated users with an ADMIN role.

To configure in memory H2 database, add the following properties to the application.properties file:

Server.port=9000


// Configure H2 in memory

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Now, we can define the components we need for our simple web service. First, we create the required models. Article.java and ArticleDTO.java contain simple models needed:

package com.atlantbh.keycloakdemo.models;

import lombok.Data;

import jakarta.persistence.*;
import java.time.Instant;

@Entity
@Data
public class Article {

  public Article() {
      this.createdAt = Instant.now();
  }

  public Article(String title, String content) {
      this.title = title;
      this.content = content;
      this.createdAt = Instant.now();
  }

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Long Id;

  @Column(name = "title")
  private String title;

  @Column(name = "content")
  private String content;

  @Column(name = "createdAt")
  private Instant createdAt;
}
package com.atlantbh.keycloakdemo.models;

import lombok.Getter;
import lombok.Setter;

import java.time.Instant;

@Getter
@AllArgsConstructor
public class ArticleDTO {
  private String title;
  private String content;
  private Instant createdAt;

  private final static int PREVIEW_LENGTH = 20;

  public static ArticleDTO minify(final Article article) {
      final int lastIndex = Math.min(article.getContent().length(), PREVIEW_LENGTH);
      final String minifiedContent = article.getContent().substring(0, lastIndex) + "...";
      return new ArticleDTO(article.getTitle(), minifiedContent, article.getCreatedAt());
  }
}

Now that we have our entity and DTO, we add a repository and a simple service that will provide data to our controller.

package com.atlantbh.keycloakdemo.repositories;

import com.atlantbh.keycloakdemo.models.Article;
import org.springframework.data.repository.CrudRepository;

public interface ArticlesRepository extends CrudRepository<Article, Long> {
}
package com.atlantbh.keycloakdemo.services;

import com.atlantbh.keycloakdemo.models.Article;
import com.atlantbh.keycloakdemo.models.ArticleDTO;
import com.atlantbh.keycloakdemo.repositories.ArticlesRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.util.List;
import java.util.stream.Collectors;

@Transactional
@Service
public class ArticlesService {
  private ArticlesRepository articlesRepository;

  public ArticlesService(final ArticlesRepository articlesRepository) {
      this.articlesRepository = articlesRepository;
  }

  public List<Article> getAll() {
      return (List<Article>) articlesRepository.findAll();
  }

  public List<ArticleDTO> getAllMinified() {
      return getAll().stream()
              .map(ArticleDTO::minify)
              .collect(Collectors.toList());
  }

  public Article createArticle(ArticleDTO article) {
      return articlesRepository.save(new Article(article.getTitle(), article.getContent()));
  }
}

Finally, now that we have everything needed, we can create Rest Controller and define our HTTP endpoints:

package controllers;

import com.atlantbh.keycloakdemo.models.Article;
import com.atlantbh.keycloakdemo.models.ArticleDTO;
import com.atlantbh.keycloakdemo.services.ArticlesService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/articles")
public class ArticlesController {

  private ArticlesService articlesService;

  public ArticlesController(final ArticlesService articlesService) {
      this.articlesService = articlesService;
  }

  @GetMapping("/all")
  public List<Article> getAll() {
      return articlesService.getAll();
  }

  @GetMapping("/minified")
  public List<ArticleDTO> getAllMinified() {
      return articlesService.getAllMinified();
  }

  @PostMapping("/create")
  public Article createArticle(@RequestBody ArticleDTO article) {
      return articlesService.createArticle(article);
  }
}

We now have a working application without security; it’s time to flavor it. Before that, we need to go back to Keycloak and do some more configuring. Specifically, we need to create and configure clients for our application. 

Creating and configuring clients

Keycloak defines clients as entities that can request authentication of a user. In our case, clients are web applications or services that use Keycloak to authenticate and authorize users. 

One client will resemble the frontend application, which should be responsible for login with Keycloak. In ideal conditions, if the user is not authenticated, he will be redirected to the Keycloak login page. After a successful login, the user is redirected back to the application. Keycloak login pages are customizable and can be modified to the needs of your application. After successful authentication, the application will receive a one-time code provided to Keycloak in exchange for identity, access, and refresh tokens. The frontend application should then include an access token in each request sent to the Spring Boot Application. Since we will focus on integrating Keycloak with Spring Security, we will invoke this API via Postman to receive the required access token for testing later.

Creating a client through the Admin Console is a simple, multistep process in the Clients menu. We will use an OIDC type named keycloak-demo-backend. In the next step, we configure the OIDC client. We will leave client authentication off (public client). We will also skip the option to enable fine-grained authorization. Regarding authentication flow, we need to enable the Standard flow to achieve the behavior we already described for frontend application. The configuration screen for the client is shown in Figure 4.

creating client Figure 4. Configuring a client for frontend application

We also need to define a client for our spring boot application,  keycloak-demo-backend. Spring boot application does not participate in the authentication flow but instead passes the access token to the Keycloak server for authentication, and it is bearer only.

Follow the same steps for creation as for the previous client, except for client authentication now being toggled ON, as this client is confidential. As mentioned, our client is bearer-only, so all authentication flows should remain unchecked.

Securing REST API

Since we aim to secure our REST API, we will configure it as a resource server. Spring-boot-starter-oauth2-resource-server is incorporated in the Spring Boot library, which makes integration of OAuth2 easier. Add the following dependencies to pom.xml:

...
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
...

We need to provide configurations for our resource server in application.properties:

spring.security.oauth2.resourceserver.jwt.issuer-uri: http://localhost:8080/realms/keycloak-demo
spring.security.oauth2.resourceserver.jwt.jwk-set-uri: http://localhost:8080/realms/keycloak-demo/protocol/openid-connect/certs

Above, we have configured our authorization server, which issues JWT to point to the realm we created. Since JWT is signed and we need to verify its signature, we set the jwk-set-uri property to a Keycloak endpoint with the required data for JWT signature verification.

JWT issued by the Keycloak server upon successful login contains realm_access and resource_access claims in which roles are incorporated. This can be verified by decoding the token.

To create custom claims on our resource server, we need a converter that will extract roles from given JWT and convert them to a collection of granted authorities. We previously opted for the realm level role, so we will extract roles from realm_access claim. 

package com.atlantbh.keycloakdemo.security;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {

  private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter;

  public JwtAuthConverter() {
      this.jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
  }

  @Override
  public AbstractAuthenticationToken convert(Jwt jwt) {
      final Set<GrantedAuthority> authorities = Stream.concat(
              jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
              extractUserRoles(jwt).stream()
      ).collect(Collectors.toSet());
      return new JwtAuthenticationToken(jwt, authorities);
  }

  private Set<? extends GrantedAuthority> extractUserRoles(Jwt jwt) {
      final Map<String, Object> realmAccess = jwt.getClaim("realm_access");
      final List<String> realmRoles = (List<String>) realmAccess.get("roles");

      if (CollectionUtils.isNotEmpty(realmRoles)) {
          return realmRoles.stream()
                  .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
                  .collect(Collectors.toSet());
      }

      return Collections.emptySet();
  }
}

Security configuration

We can now add the security configuration class. 

package com.atlantbh.keycloakdemo.security;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {

  private final JwtAuthConverter jwtAuthConverter;

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
      http.cors().and().csrf().disable();
      http.authorizeHttpRequests()
              .requestMatchers("/articles/minified").permitAll()
              .requestMatchers("/articles/create").hasRole("ADMIN")
              .requestMatchers("/articles/all").hasRole("USER")
              .anyRequest().authenticated();
      http.oauth2ResourceServer()
              .jwt()
              .jwtAuthenticationConverter(jwtAuthConverter);
      http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
      return http.build();
  }
}

Using the SecurityFilterChain bean, we can override the default security configuration. Our application does not create nor maintain sessions; therefore, we will use the STATELESS session creation policy. We specify that the application behaves as a resource server using jwt, and we provide our custom converter to go from JWT to granted authorities.

Finally, this filter chain enforces authentication and role-based security policies. CORS and CSRF were disabled, and authentication of all requests is required, except on route /articles/minified. Additionally, routes /articles/create and /articles/all require appropriate roles. 

With this, we have finished our Keycloak integration, and we can test the endpoints. Verify that the endpoint is publicly accessible by sending a GET request via Postman to localhost:9000/articles/minified, with the authorization type set to none. 

To check secured routes, we first need to simulate logging in to Keycloak, which, in an ideal scenario, would be handled by the frontend application. To achieve this, we will use a public client previously created for the frontend application.

Open Postman and create a POST request to the Keycloak endpoint, as demonstrated in Figure 5. Keycloak accepts parameters in the request body only using application/x-www-form-urlencoded format. 

Keycloak and Spring SecurityFigure 5. Obtaining access token from Keycloak

Now, we can use the obtained access token for our succeeding requests. 

We have secured our application using Spring Security and Keycloak, which successfully handles authentication and authorization. 

If you found this useful, check out other Atlantbh blogs!

Leave a Reply