π 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
- Client Request β Any protected endpoint (
/admin/**
). - Spring Security Filter Chain β intercepts request; checks authentication.
- Redirect to Keycloak β if user is not logged in (web app flow).
- Keycloak Authentication β user enters credentials; Keycloak validates and issues tokens.
- Spring Security Token Validation β JWT signature, expiration, and roles/claims verification.
- Populate SecurityContext β Spring stores
Authentication
object (user + authorities). - Authorization Check β Spring evaluates endpoint roles/permissions (
hasRole
,@PreAuthorize
). - 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
- 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
- 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
- Client ID:
- Create a user and assign a role (e.g.,
user
,admin
).
- Login with
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 neededhttp://localhost:8081/user/hello
β login via Keycloakhttp://localhost:8081/admin/hello
β requiresadmin
role