Securing a Spring Boot Application with Keycloak - A First Look

Learn how to: set up a Spring Boot application for a public library, define the application resources, add access policies based on user roles. We're going to use OAuth 2.0 and OpenID Connect, specifically the standard Authorization Code Flow.

Securing a Spring Boot Application with Keycloak - A First Look

In this article, we're going to have a first look at how to secure a Spring Boot application using Keycloak.

Before doing that, let's sum up what we have done so far. First, we talked about the main features of Keycloak used in this series and learned how to install and boot the Keycloak server.

Then, we set Keycloak up with some basic configurations to use it for securing a web application (providing it with authentication and authorization).

Finally, we learned about authentication flows and SSO protocols, and by using that knowledge, we have defined a client in Keycloak that we're going to use to secure a Spring Boot application.

In this article, we will learn how to:

  • Set up a Spring Boot application for Keycloak;
  • Configure the Keycloak integration with Spring Boot
  • Define the application resources;
  • Add access policies based on user roles.

You can check out the full source code of the demo project we're going to build on GitHub.

Let's get started!

2. The Demo Application

We're going to develop an application for a public library. Members will be able to browse the books available in the library. Librarians will also have the chance to manage the books.

3. Set up a Spring Boot application

Time to code!

We can make our application interact with Keycloak very smoothly, thanks to the so-called Client Adapters.

Keycloak client adapters are libraries that make it very easy to secure applications and services with Keycloak.

For this project, we need the Spring Boot Adapter.

Using Gradle, we can define the required dependencies in the build.gradle file.

buildscript {
	ext {
		springBootVersion = '2.1.9.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.thomasvitale'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

ext {
	set('keycloakVersion', '7.0.1')
}

dependencies {
	// Spring
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-devtools'
	
	// Keycloak
	implementation 'org.keycloak:keycloak-spring-boot-starter'
	
	// Test
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
	imports {
		mavenBom "org.keycloak.bom:keycloak-adapter-bom:${keycloakVersion}"
	}
}
build.gradle

We're using Spring Boot 2.1.x to build our application. The keycloakVersion is 7.0.1.

The next step is configuring our Spring Boot application to use Keycloak. Let's open up the application.properties  (or application.yml) file and write the following configuration.

keycloak.realm=public-library
keycloak.resource=spring-boot-app
keycloak.auth-server-url=http://localhost:8180/auth
keycloak.ssl-required=external
keycloak.public-client=true
application.properties

Let's quickly go over each property:

  • keycloak.realm: the name of the realm, required;
  • keycloak.resource: the client-id of the application, required;
  • keycloak.auth-server-url: the base URL of the Keycloak server, required;
  • keycloak.ssl-required: establishes if communications with the Keycloak server must happen over HTTPS. Here, it's set to external, meaning that it's only needed for external requests (default value). In production, instead, we should set it to all. Optional;
  • keycloak.public-client: prevents the application from sending credentials to the Keycloak server (false is the default value). We want to set it to true whenever we use public clients instead of confidential. Optional.

4. Configure Keycloak for Spring Boot

Starting from Spring Boot Keycloak Adapter 7.0.0, we are required to explicitly define a KeycloakSpringBootConfigResolver bean to make Spring Boot resolve the Keycloak configuration from application.properties  (or application.yml) correctly. It must be defined in a @Configuration class.

There is an open issue about it on the Keycloak project, in case you're interested in getting more details.

So, let's add our bean.

@Configuration
public class KeycloakConfig {

    @Bean
    public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
}
KeycloakConfig.class

Without this configuration, we would get an error while starting up Spring Boot.

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 1 of method setKeycloakSpringBootProperties in org.keycloak.adapters.springboot.KeycloakBaseSpringBootConfiguration required a bean of type 'org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver' that could not be found.


Action:

Consider defining a bean of type 'org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver' in your configuration.


Process finished with exit code 1
Error thrown when no KeycloakSpringBootConfigResolver bean has been defined.

5. Define the application resources

To demonstrate how Keycloak can handle authentication and authorization for a Spring Boot application, we'll define three resources:

  • /index will be freely accessible;
  • /books will be accessible only by users with standard privileges (Member role), who can browse the books available at the library;
  • /manager will be accessible only by users with administrative privileges (Librarian role), who can manage the books.

The last two resources require users both to be authenticated and have the proper role. It's also helpful adding a fourth resource for logging out.

@Controller
public class LibraryController {
	
	private final HttpServletRequest request;
	private final BookRepository bookRepository;

	public LibraryController(HttpServletRequest request, BookRepository bookRepository) {
		this.request = request;
		this.bookRepository = bookRepository;
	}

	@GetMapping(value = "/")
	public String getHome() {
		return "index";
	}

	@GetMapping(value = "/books")
	public String getBooks(Model model) {
		configCommonAttributes(model);
		model.addAttribute("books", bookRepository.readAll());
		return "books";
	}

	@GetMapping(value = "/manager")
	public String getManager(Model model) {
		configCommonAttributes(model);
		model.addAttribute("books", bookRepository.readAll());
		return "manager";
	}

	@GetMapping(value = "/logout")
	public String logout() throws ServletException {
		request.logout();
		return "redirect:/";
	}

	private void configCommonAttributes(Model model) {
		model.addAttribute("name", getKeycloakSecurityContext().getIdToken().getGivenName());
	}

	private KeycloakSecurityContext getKeycloakSecurityContext() {
		return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
	}
}
LibraryController.java

LibraryController is a Spring MVC Controller. It's worth noticing how we're using the KeycloakSecurityContext to retrieve the IdToken, from which we can get the first name of the authenticated user.

To provide the app with some basic functionality, we're using an in-memory BookRepository class which allows read all the Book entities stored in it.

public class Book {
    private String id;
    private String title;
    private String author;

    public Book(String id, String title, String author) {
        this.id = id;
        this.title = title;
        this.author = author;
    }

    // Getters and setters
}
Book.java
@Repository
public class BookRepository {
    private static Map<String, Book> books = new ConcurrentHashMap<>();

    static {
        books.put("B01", new Book("B01", "Harry Potter and the Deathly Hallows", "J.K. Rowling"));
        books.put("B02", new Book("B02", "The Lord of the Rings", "J.R.R. Tolkien"));
        books.put("B03", new Book("B03", "War and Peace", "Leo Tolstoy"));
    }

    public List<Book> readAll() {
        List<Book> allBooks = new ArrayList<>(books.values());
        allBooks.sort(Comparator.comparing(Book::getId));
        return allBooks;
    }
}
BookRepository.java

As a template engine, we're using Thymeleaf. We have a template for each resource as well as a special template to handle unauthorized requests. You can check out the full source code of this demo project on GitHub.

6. Add access policies based on user roles

So far, we can run the application and navigate it freely through a browser. Even though we have correctly configured and integrated Keycloak into our application, we haven't defined yet which resources we want to protect and which privileges a user needs to access them.

It's the perfect job for Spring Security, but we'll use that in the next article. Here, to have a first look at how Keycloak works in the context of a web application, we're going to exploit the plain Spring Boot configuration.

Let's define the access policies in application.properties  (or application.yml).

keycloak.securityConstraints[0].authRoles[0]=Member
keycloak.securityConstraints[0].authRoles[1]=Librarian
keycloak.securityConstraints[0].securityCollections[0].name=member resource
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/books

keycloak.securityConstraints[1].authRoles[0]=Librarian
keycloak.securityConstraints[1].securityCollections[0].name=librarian resource
keycloak.securityConstraints[1].securityCollections[0].patterns[0]=/manager
application.properties

We want to define two security constraints, one for each resource to protect. For each security constraint, we set the authorization roles that a user must have to access a protected resource. Then, we specify a name and a pattern for the URL associated with the resource.

The second row is not even needed. Since we defined the Librarian role as composite, it automatically has Member privileges as well.

When a user tries to access a protected resource, they are redirected to Keycloak that authenticates them. If the authentication succeeds, Keycloak redirects the user to the application.

At this point, Keycloak sends back to the application an IdToken with the information about the identity of the user. It also provides an AccessToken containing the information relevant to the authorization of the user, including the user roles. If they don't have the role needed to access the resource, Spring Boot will show an error page.

Conclusion

In this article, we have seen how to use Keycloak to get authentication and authorization services for a Spring Boot application.

The essential parts have been the use of the Keycloak Spring Boot Adapter, the Keycloak configuration and the access policies definition.

You can check out the full source code of the demo project on GitHub.

Next time, we'll use Spring Security to have more control while still relying on Keycloak.

Have you secured your application using Keycloak? Leave a comment and let me know about it!

Last update: 14/11/2019

Keycloak Series

Keycloak with Spring Series


If you're interested in cloud native development with Spring Boot and Kubernetes, check out my book Cloud Native Spring in Action.