DEV Community

Cover image for Keycloak and Spring Boot: The Ultimate Guide to Implementing Single Sign-On
Tandap Noel Bansikah
Tandap Noel Bansikah

Posted on • Updated on

Keycloak and Spring Boot: The Ultimate Guide to Implementing Single Sign-On

Introduction:

Single Sign-On (SSO) has become an essential feature in modern web applications, enhancing both user experience and security. This comprehensive guide will walk you through implementing SSO using Keycloak and Spring Boot, providing a robust authentication and authorization solution for your applications.

Importance of SSO with Keycloak

Single Sign-On (SSO) is essential for streamlining authentication processes, enhancing security, and improving user experience. Here are some of the key benefits:

  1. Centralized Authentication: SSO allows users to authenticate once and gain access to multiple applications. Keycloak provides centralized management for user identities, which is useful in environments with numerous applications.

  2. Improved Security: With centralized identity management, security policies (like password strength, two-factor authentication, and account lockout policies) can be uniformly enforced. Keycloak’s support for protocols like OpenID Connect and OAuth 2.0 ensures robust, modern security standards.

  3. Reduced Password Fatigue and Enhanced User Experience: By logging in only once, users avoid password fatigue and multiple credentials, leading to smoother and faster interactions across applications.

  4. Scalability and Flexibility: Keycloak’s configuration can support large numbers of users and multiple identity providers, including social logins (Google, Facebook, etc.) and enterprise directories (LDAP, Active Directory).

  5. Customization and Extensibility: Keycloak allows custom themes, login flows, and extensions, making it adaptable to various needs. It’s also open-source, providing flexibility for organizations to modify or extend the platform as required.

Alternatives to Single Sign-On (SSO):

  1. Multiple Sign-On / Traditional Authentication:

    • Users have separate credentials for each application or service
    • Requires logging in individually to each system
    • Each application manages its own authentication
  2. Federated Identity:

    • Similar to SSO, but allows authentication across different organizations
    • Uses standards like SAML or OpenID Connect
    • User's identity is verified by their home organization
  3. Multi-Factor Authentication (MFA):

    • Adds additional layers of security beyond just username and password
    • Can be used alongside SSO or traditional authentication
    • Typically involves something you know, have, and are

SSO Flow Explanation:

Before diving into the implementation, let's understand the SSO flow:

SSO Flow

Prerequisites:

Step 1: Project Setup

Create a new Spring Boot project using Spring Initializr or intelliJ with the following structure:

keycloak-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── bansikah/
│   │   │           └── keycloakdemo/
│   │   │               ├── config/
│   │   │               │   └── SecurityConfig.java
│   │   │               ├── controller/
│   │   │               │   └── FoodOrderingController.java
│   │   │               └── KeycloakDemoApplication.java
│   │   └── resources/
│   │       ├── templates/
│   │       │   ├── home.html
│   │       │   └── menu.html
│   │       └── application.yml
├── docker-compose.yml
└── pom.xml
Enter fullscreen mode Exit fullscreen mode

Note:

bansikah is my name 😂 so you can put yours or example anything you want...

Step 2: Configure pom.xml

Add the following dependencies to your pom.xml or you can just replace the dependency section to avoid conflicts:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity3 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity3</artifactId>
            <version>3.0.5.RELEASE</version>
        </dependency>
    </dependencies>
Enter fullscreen mode Exit fullscreen mode

Step 3: Set up Keycloak with Docker

Create a docker-compose.yml file in the root directory:

version: '3'

services:
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    ports:
      - "8088:8080"
    command:
      - start-dev

  app:
    build: .
    ports:
      - "8082:8082"
    depends_on:
      - keycloak
Enter fullscreen mode Exit fullscreen mode

Run the Keycloak server with:

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Step 4: Configure Keycloak

  1. Access the Keycloak Admin Console:

  2. Create a New Realm:

    • Go to "Master" at the top left corner
    • Select "Add realm"
    • Name it food-ordering-realm
    • Click "Create"
  3. Create a New Client:
    On the first screen:

    • Set the "Client ID" to "food-ordering-client"
    • Client type: Select "OpenID Connect"
    • Click "Next"

On the next screen (Capability config):

  • Client authentication: Turn this ON (this replaces the old "confidential" setting)
  • Authorization: You can leave this OFF unless you need fine-grained authorization
  • Click "Next"
  1. Client Configuration:
    • Set the root url to http://localhost:8082/
    • Set Access Type to confidential
    • Add Valid Redirect URIs (each URI on a new line):
 http://localhost:8082/
 http://localhost:8082/menu
 http://localhost:8082/login/oauth2/code/keycloak
Enter fullscreen mode Exit fullscreen mode

Image one

  1. Retrieve the Client Secret:
    • Go to the Credentials tab
    • Copy the Secret field's value for use in the application configuration

Image two

  1. Create a User:
    • Go to Users and click "Add user"
    • Set a username (e.g., testuser)
    • In Credentials tab:
      • Set a password
      • Disable "Temporary"

Image three
and set the password:

Image four

Step 5: Configure Spring Boot Application

Create application.yml in src/main/resources:

server:
  port: 8082

spring:
  application:
    name: keycloak-demo
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: food-ordering-client
            client-secret: your-client-secret
            scope: openid,profile,email
            redirect-uri: http://localhost:8082/login/oauth2/code/keycloak
        provider:
          keycloak:
            issuer-uri: http://localhost:8088/realms/food-ordering-realm

logging:
  level:
    org.springframework.security: DEBUG
    org.springframework.security.oauth2: DEBUG

Enter fullscreen mode Exit fullscreen mode

Replace<your-client-secret> with the copied secret from Keycloak, usually some random text.
Note:

In production or as a good practice it will be good to keep delicate information as such in a .env file at the root of your project and use it as a variable in your configuration it will be something like ${CLIENT_SECRET} which picks it from the .env file when you start your application, this is also applicable to even the redirect, issuer-uri and the rest..

Step 6: Create Security Configuration

Create SecurityConfig.java in src/main/java/com/bansikah/keycloakdemo/config:

package com.bansikah.keycloakdemo.config;

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.web.SecurityFilterChain;

/**
 * SecurityConfig class configures security settings for the application,
 * enabling security filters and setting up OAuth2 login and logout behavior.
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    /**
     * Configures the security filter chain for handling HTTP requests, OAuth2 login, and logout.
     *
     * @param http HttpSecurity object to define web-based security at the HTTP level
     * @return SecurityFilterChain for filtering and securing HTTP requests
     * @throws Exception in case of an error during configuration
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // Configures authorization rules for different endpoints
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/").permitAll() // Allows public access to the root URL
                        .requestMatchers("/menu").authenticated() // Requires authentication to access "/menu"
                        .anyRequest().authenticated() // Requires authentication for any other request
                )
                // Configures OAuth2 login settings
                .oauth2Login(oauth2 -> oauth2
                        .loginPage("/oauth2/authorization/keycloak") // Sets custom login page for OAuth2 with Keycloak
                        .defaultSuccessUrl("/menu", true) // Redirects to "/menu" after successful login
                )
                // Configures logout settings
                .logout(logout -> logout
                        .logoutSuccessUrl("/") // Redirects to the root URL on successful logout
                        .invalidateHttpSession(true) // Invalidates session to clear session data
                        .clearAuthentication(true) // Clears authentication details
                        .deleteCookies("JSESSIONID") // Deletes the session cookie
                );

        return http.build();
    }
}


Enter fullscreen mode Exit fullscreen mode

Step 7: Create Controller

Create FoodOrderingController.java in src/main/java/com/bansikah/keycloakdemo/controller:

package com.bansikah.keycloakdemo.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * FoodOrderingController handles web requests related to the home and menu pages of the food ordering application.
 */
@Controller
public class FoodOrderingController {

    /**
     * Maps the root URL ("/") to the home page.
     *
     * @return the name of the view to render for the home page
     */
    @GetMapping("/")
    public String home() {
        return "home";
    }

    /**
     * Maps the "/menu" URL to the menu page and sets the authenticated user's username in the model.
     *
     * @param user  the authenticated OIDC (OpenID Connect) user
     * @param model Model object for passing data to the view
     * @return the name of the view to render for the menu page, or redirects to home if user is not authenticated
     */
    @GetMapping("/menu")
    public String menu(@AuthenticationPrincipal OidcUser user, Model model) {
        if (user != null) {
            model.addAttribute("username", user.getPreferredUsername());
        } else {
            return "redirect:/";  // Redirect to home if not authenticated
        }
        return "menu";
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 8: Create HTML Templates

Create home.html in src/main/resources/templates:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Welcome to Food Ordering</title>
</head>
<body>
<h1>Welcome to Food Ordering</h1>
<p>Click <a th:href="@{/menu}">here</a> to view the menu (requires login).</p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Create menu.html in src/main/resources/templates:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Food Ordering Menu</title>
</head>
<body>
<h1>Welcome to the Menu, <span th:text="${username}"></span>!</h1>
<p>Here's our menu (placeholder):</p>
<ul>
    <li>Pizza - $10</li>
    <li>Burger - $8</li>
    <li>Salad - $6</li>
</ul>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="Logout"/>
</form>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Step 9: Run the Application

You should be able to access the application on this url http://localhost:8082 and you should see this

Image Five
and when you click the here link it takes you to the keycloak form where the user has to authenticate with username and password under the foodorder realm

Image six

and after authenticated you will see the menu page

Image seven

Now with the importance of SSO, even if i logout, i won't have to login again as below

Image Eight
then i can click the link again and i won't be propted to enter my password and username again as below

Image Nine

How it works
Keycloak issues an access token and a refresh token upon user login. For SSO, it shares these tokens across authorized applications in the realm, allowing the user to access multiple apps without re-authenticating. When the access token expires, the refresh token is used to renew it, keeping the session active across apps.

Conclusion

Congratulations 😊, and Thank you for following up till this time
This implementation demonstrates a robust SSO solution using Keycloak and Spring Boot. It provides a seamless authentication experience while maintaining security. The configuration allows for easy customization and extension to meet specific application needs.
If you encounter any issues or have questions about this implementation, please feel free to leave a comment below. Remember to check the Spring Security and Keycloak documentation for more advanced configurations and features.
Link to code on github

Ref:

Top comments (0)