Keycloak in Spring Boot

πŸ” Spring Security + Keycloak Flow Schema

[Client Request] 
    β”‚
    β”‚ GET /admin/dashboard
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Spring Security Filter Chainβ”‚
β”‚ ────────────────────────── β”‚
β”‚ Filters:                    β”‚
β”‚ - OAuth2LoginAuthentication β”‚
β”‚ - BearerTokenAuthentication β”‚
β”‚ - SecurityContextPersistence β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β”‚ No authentication?
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Redirect to Keycloak Login  β”‚
β”‚ (Authorization Endpoint)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β”‚ User submits credentials
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Keycloak Server             β”‚
β”‚ - Validates user            β”‚
β”‚ - Creates session           β”‚
β”‚ - Issues tokens:            β”‚
β”‚   * ID Token                β”‚
β”‚   * Access Token (JWT)      β”‚
β”‚   * Refresh Token           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β”‚ Redirects back to Spring Boot
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Spring Security Receives JWTβ”‚
β”‚ - Validates signature       β”‚
β”‚ - Checks expiration & claimsβ”‚
β”‚ - Extracts roles/authoritiesβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SecurityContextHolder       β”‚
β”‚ - Stores Authentication     β”‚
β”‚ - Contains user + roles     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Authorization Check         β”‚
β”‚ - AccessDecisionManager     β”‚
β”‚ - Role/authority check      β”‚
β”‚ - @PreAuthorize / URL rules β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
Access Decision
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Access Granted β”‚ Access Denied β”‚
  β”‚ Controller     β”‚ 403 Forbidden β”‚
  β”‚ executes       β”‚               β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”Ž Detailed Description

  1. Client Request β†’ Any protected endpoint (/admin/**).
  2. Spring Security Filter Chain β†’ intercepts request; checks authentication.
  3. Redirect to Keycloak β†’ if user is not logged in (web app flow).
  4. Keycloak Authentication β†’ user enters credentials; Keycloak validates and issues tokens.
  5. Spring Security Token Validation β†’ JWT signature, expiration, and roles/claims verification.
  6. Populate SecurityContext β†’ Spring stores Authentication object (user + authorities).
  7. Authorization Check β†’ Spring evaluates endpoint roles/permissions (hasRole, @PreAuthorize).
  8. Access Decision β†’ request proceeds if authorized; otherwise, 403 Forbidden.

This schema clearly separates:

  • Client β†’ App β†’ Keycloak interactions
  • Filters β†’ Token validation β†’ SecurityContext β†’ Authorization
  • Outcome: Granted or Denied

πŸ”‘ What is Keycloak?

Keycloak is an open-source Identity and Access Management (IAM) solution. It provides:

  • Single Sign-On (SSO)
  • Login/Logout
  • Role-based Access Control (RBAC)
  • OAuth2 / OpenID Connect support

Instead of handling authentication manually, Spring Boot can delegate it to Keycloak.


βš™οΈ Steps to Integrate Spring Boot with Keycloak

1. Set up Keycloak

  1. Download and run Keycloak: docker run -d --name keycloak \ -p 8080:8080 \ -e KEYCLOAK_ADMIN=admin \ -e KEYCLOAK_ADMIN_PASSWORD=admin \ quay.io/keycloak/keycloak:23.0.1 start-dev
  2. Go to http://localhost:8080/
    • Login with admin/admin
    • Create a realm (e.g., spring-realm)
    • Create a client:
      • Client ID: spring-client
      • Root URL: http://localhost:8081/
      • Valid redirect URI: http://localhost:8081/*
      • Client authentication: On
    • Create a user and assign a role (e.g., user, admin).

2. Spring Boot Project Setup

Add dependencies in build.gradle or pom.xml.

pom.xml

<dependencies>
    <!-- Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- OAuth2 Client Resource Server -->
    <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-oauth2-client</artifactId>
    </dependency>
</dependencies>

3. Application Properties

application.yml

server:
  port: 8081

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: spring-client
            client-secret: YOUR_CLIENT_SECRET
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          keycloak:
            issuer-uri: http://localhost:8080/realms/spring-realm
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/realms/spring-realm

Replace YOUR_CLIENT_SECRET (from Keycloak β†’ Clients β†’ spring-client β†’ Credentials).


4. Security Config

Spring Boot 3+ uses the new SecurityFilterChain.

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

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated()
            )
            .oauth2Login()   // Login with Keycloak
            .and()
            .oauth2ResourceServer().jwt(); // API token validation

        return http.build();
    }
}

5. Controller Example

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;

@RestController
public class DemoController {

    @GetMapping("/public/hello")
    public String publicHello() {
        return "Hello, world (no auth needed)";
    }

    @GetMapping("/user/hello")
    public String userHello(Principal principal) {
        return "Hello, " + principal.getName();
    }

    @GetMapping("/admin/hello")
    public String adminHello(Principal principal) {
        return "Admin Hello, " + principal.getName();
    }
}

6. Run the Application

  • Start Keycloak (http://localhost:8080/)
  • Run Spring Boot (http://localhost:8081/)
  • Visit:
    • http://localhost:8081/public/hello β†’ no login needed
    • http://localhost:8081/user/hello β†’ login via Keycloak
    • http://localhost:8081/admin/hello β†’ requires admin role

Leave a Reply