This guide provides a comprehensive integration of Spring Boot 3, Spring Security 6, and Keycloak (OpenID Connect/OAuth 2.0) for securing REST APIs. We'll cover:
- Keycloak Setup & Configuration
- Spring Boot 3 + Spring Security 6 Integration
- Role-Based Access Control (RBAC)
- Postman Testing Guide
1. Keycloak Setup & Configuration
1.1 Run Keycloak via Docker
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:22.0.0 start-dev
Access Keycloak Admin Console: http://localhost:8080/admin
1.2 Configure Keycloak
- Create a Realm (
springboot-realm
) - Create a Client (
springboot-app
)- Client ID:Â
springboot-app
- Root URL:Â
http://localhost:8081
 (Spring Boot app port) - Valid Redirect URIs:Â
http://localhost:8081/*
- Web Origins:Â
http://localhost:8081
- Access Type:Â
confidential
- EnableÂ
Client credentials
 &ÂAuthorization code
 flows.
- Client ID:Â
- Create Roles (
ROLE_ADMIN
,ÂROLE_USER
) - Create Users & Assign Roles
- User:Â
user1
 → AssignÂROLE_USER
- User:Â
admin1
 → AssignÂROLE_ADMIN
- User:Â
- Get Client Secret
- Go toÂ
Clients
 →Âspringboot-app
 →ÂCredentials
 → CopyÂSecret
.
- Go toÂ
2. Spring Boot 3 + Spring Security 6 Integration
2.1 Dependencies (pom.xml
)
<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-security</artifactId>
</dependency>
2.2 Configure application.yml
server:
port: 8081
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/springboot-realm
jwk-set-uri: http://localhost:8080/realms/springboot-realm/protocol/openid-connect/certs
2.3 Security Configuration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> {}));
return http.build();
}
}
2.4 Controller with Role-Based Access
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class DemoController {
@GetMapping("/user")
@PreAuthorize("hasRole('ROLE_USER')")
public String userEndpoint() {
return "Hello, User!";
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String adminEndpoint() {
return "Hello, Admin!";
}
@GetMapping("/public")
public String publicEndpoint() {
return "Public API";
}
}
3. Postman Testing Guide
3.1 Get Access Token (Password Grant Flow)
Request:
POST http://localhost:8080/realms/springboot-realm/protocol/openid-connect/token Headers: - Content-Type: application/x-www-form-urlencoded Body (form-data): - grant_type: password - client_id: springboot-app - client_secret: [YOUR_CLIENT_SECRET] - username: user1 - password: [USER_PASSWORD] - scope: openid
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 300
}
3.2 Access Protected API
Request:
GET http://localhost:8081/api/user Headers: - Authorization: Bearer [ACCESS_TOKEN]
Expected Response:
Hello, User!
3.3 Test Admin Endpoint (Should Fail for ROLE_USER
)
Request:
GET http://localhost:8081/api/admin Headers: - Authorization: Bearer [ACCESS_TOKEN]
Expected Response:
{
"error": "access_denied",
"error_description": "Not authorized"
}
3.4 Public Endpoint (No Token Needed)
Request:
GET http://localhost:8081/api/public
Expected Response:
Public API
Next Steps
- Refresh Tokens: Implement token refresh logic.
- Custom Claims: Extract additional user info from JWT.