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:
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.
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.
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.
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).
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):
-
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
-
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
-
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:
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
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>
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
Run the Keycloak server with:
docker-compose up -d
Step 4: Configure Keycloak
-
Access the Keycloak Admin Console:
- Go to http://localhost:8088
- Log in with
admin/admin
as username and password
-
Create a New Realm:
- Go to "Master" at the top left corner
- Select "Add realm"
- Name it
food-ordering-realm
- Click "Create"
-
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"
- 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
- Set Web Origins: http://localhost:8082
- Click "Save"
- Retrieve the Client Secret:
- Go to the
Credentials
tab - Copy the
Secret
field's value for use in the application configuration
- Go to the
- Create a User:
- Go to
Users
and click "Add user" - Set a username (e.g., testuser)
- In
Credentials
tab:- Set a password
- Disable "Temporary"
- Go to
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
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();
}
}
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";
}
}
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>
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>
Step 9: Run the Application
You should be able to access the application on this url http://localhost:8082 and you should see this
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
and after authenticated you will see the menu
page
Now with the importance of SSO, even if i logout, i won't have to login again as below
then i can click the link again and i won't be propted to enter my password and username again as below
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)