Mirza Mesihovic
2 articles
November 15, 2023
Software Development
Securing Spring Boot Applications with Keycloak and Spring Security
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. Figure 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 here. After 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. Figure 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. Figure 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. 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. Figure 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!
June 9, 2022
Software Development
Introduction to Adapter design pattern During your journey through life as a software developer, you will face various problems. Naturally, you want to follow the best practices while writing code and make life easier for yourself and your colleagues. Knowledge of software design patterns which are reusable solutions of common problems in software development, is of great value. Adapter design pattern is a structural design pattern which enables two classes with different interfaces but same underlying functionality to work together. It converts the interface of one class to another expected interface. You are working on expanding a feature in your application and you want to use reuse some legacy class. However, that legacy class does not provide the interface you require and you do not want to change it. In this situation, adapter pattern is a good solution. In another scenario, you are required to switch from an internal component to an external one which provides the same functionality but is better suited. The issue you run into is that the interface of the external component is slightly different. Your existing component is used all across the project and replacing it requires code modifications in multiple places which could easily lead to bugs. Using the adapter pattern, you can write an adapter class which will wrap around an external component and provide the required interface. In that way, the client does not know they are interacting with a different underlying component, and our existing component can now be replaced with the adapter. How do we implement it? To summarize the issue, objects that participate in adapter pattern are often defined as following: Target Interface - This is the required interface which client expects Adaptee - Class which has an incompatible interface and has to be adapted Adapter - Class that wraps around the adaptee, implements the target interface and delegates the requests to the adaptee Client - Class which uses and interacts with the adapter class It is worth mentioning that there are two types of adapter implementations which use different approaches. Class adapters use inheritance for implementation and are mostly used in languages that support multiple inheritances. Object adapters use composition to achieve the same results and are a common way of implementation, since they follow the composition over inheritance principle. This tech bite shows the implementation of an object adapter through the example below. Example You are working on a big project which involves multiple services/applications whose usage statistics have to be analyzed. Your application uses the following interface to obtain data from specific services. This is our target interface: public interface IAppStatistics { Map<String, List<String>> getUserActivity(List<User> users, Date fromDate, Date untilDate) List<Triple<String, String, String>> getBrowserUsage(List<Pair<String,String>> browsers) Map<String, String> getGeneralAppStatistics(); String getErrorSummary(String errorType, Date fromDate, Date untilDate) } A new service has been added to the ecosystem, developed by an external team who has already provided an interface for fetching all the data you need, which looks like this: interface IExternalServiceStatistics { Map<String, List<String>> getUserActivity(List<String> userIds, Date fromDate, Date untilDate) String getBrowserActivity(String browser, String os) Map<String, String> getGeneralStatistics(); String getErrorSummary(String errorType, Date fromDate, Date untilDate) } At our disposal, we also have the ExternalServiceStatistics class which implements the IExternalServiceStatistics interface. This is the adaptee class which we need to adapt for our own use. Before writing our Adapter class, we need to check the differences in the two interfaces: Method getUserActivity of ExternalServiceStatistics class receives a list of user ID’s rather than a list of User objects. Method getBrowserActivity differs by return type as well as method parameters. Method getGeneralAppStatistics is differently named in the ExternalServiceStatistics class. Method getErrorSummary has same signature and can be delegated without any changes. Once we know what we need to adapt, we can now write the adapter class. We are going to achieve this through composition. Our adapter class called ExternalServiceStatisticsAdapter will have one property, which is the adaptee and will implement our target interface IAppStatistics: public class ExternalServiceStatisticsAdapter implements IAppStatistics { // Instance of adaptee class ExternalServicesStatistics adaptee; public ExternalServiceStatisticsAdapter(ExternalServicesStatistics adaptee) { this.adaptee = adaptee; } @Override public Map<String, List<String>> getUserActivity(List<User> users, Date fromDate, Date untilDate) { // Extract list of IDs for adaptee method call final List<String> userIds = users.stream().map(user -> user.getId()).collect(Collectors.toList()); return adaptee.getUserActivity(userIds, fromDate, untilDate); } @Override public List<Triple<String, String, String>> getBrowserActivity(List<Pair<String,String> browsers) { // Note: for this you need org.apache.commons.lang3 dependency final List<Triple<String, String, String>> results = new ArrayList<>(); browsers.forEach(pair -> results.add(Triple.of(pair.getLeft(), pair.getRight(), adaptee.getBrowserActivity(pair.getLeft(), pair.getRight()))); return results; } @Override public Map<String, String> getGeneralAppStatistics() { //Just a different name return adaptee.getGeneralStatistics(); } @Override public String getErrorSummary(String errorType, Date fromDate, Date untilDate) { //Method signature matches, just delegate it to adaptee return adaptee.getErrorSummary(errorType, fromDate, untilDate) } } Now our client class can use the ExternalServiceStatisticsAdapter class to obtain data using the desired interface. "Adapter Pattern in Java" Tech Bite was brought to you by Mirza Mesihović, Software Engineer at Atlantbh. (more…)
Ready to Achieve More?
We’ll help you reach your goals quickly with an easy and straightforward process to kick off our collaboration. Here’s what happens next.