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're going to learn how to:

  1. Set up the Spring Boot application;
  2. Define the application resources;
  3. 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 the Spring Boot application

Time to code!

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

"Keycloak client adapters are libraries that makes 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.7.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', '6.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}"
	}
}

We're going to use Spring Boot 2.1.x to build our application. We set the keycloakVersion property to 6.0.1. At the moment, there is an issue in using the Keycloak Spring Boot adapter 7.0.0, so let's stick with 6.0.1 until it's fixed. Notice that, no matter the adapter, we can still use the Keycloak server 7.0.0 we set up in the previous articles.

The next step is configuring our Spring Boot application to use Keycloak. Let's open the application.properties 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

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 just required for external requests (default value), but in production, 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. 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 normal 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 the users both to be authenticated and have the proper role. It's also helpful adding a fourth resource for logging out. Here's the code:

@Controller
public class ApplicationController {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private 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());
    }
}

This is a normal Controller as used in every Spring MVC application. 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. In a future article we're going to talk more about this object and what can we do with it.

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
}
@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;
    }
}

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.

5. 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 the application.properties file:

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

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.

Actually, the second row is not 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 will be redirected to Keycloak that authenticates them. If the authentication succeeds, Keycloak will redirect the user to the application.

At this point, the application will have received by Keycloak an IdToken with the information about the identity of the user and an Access Token containing all 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 Spring Boot Adapter, and the Keycloak configuration and the access policies definition in the application.properties.

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. Also, we'll go over the information associated with each user and how it can be retrieved from the KeycloakSecurityContext.

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

Last update: 04/09/2019

Keycloak Series

Keycloak with Spring Series

  • Securing a Spring Boot Application with Keycloak - A First Look