RESTful resource server built with Spring Boot that integrates with Keycloak for authentication/authorization. The service has one resource, /api/users that is meant to be modified to suit the application user needs. The /api/keycloak resources are the KC side and not meant for extending. It exposes endpoints to work with:
- Users in this service (
/api/users) - Keycloak Users (
/api/keycloak/users) - Keycloak Clients (
/api/keycloak/clients) - Keycloak Roles (
/api/keycloak/roles)
The service is secured as an OAuth2 Resource Server (JWT) and expects a Bearer token issued by Keycloak.
- Language: Java 21 (see
pom.xml→<java.version>21</java.version>) - Framework: Spring Boot 3.5.7 (Web, Security, OAuth2 Resource Server, Data JPA)
- Database Driver: PostgreSQL (runtime dependency)
- Build/Package: Maven (Maven Wrapper included:
mvnw/mvnw.cmd) - Testing: JUnit (Spring Boot Test, Spring Security Test), integration tests via Maven Failsafe (
*IT.java) - Container: Dockerfile provided
- Java 21 JDK
- Maven 3.9+ (or just use the Maven Wrapper
./mvnw) - PostgreSQL database (if you enable datasource properties)
- A running Keycloak instance with a realm/clients configured to issue JWTs for this API
bin/kc.sh start-dev --hostname keycloak --db postgres --db-url-host keycloak-postgres --db-username postgres --db-password password
Application defaults (see src/main/resources/application.properties):
server.port=8081spring.jpa.show-sql=truespring.jpa.generate-ddl=truespring.jpa.hibernate.ddl-auto=updatelogging.level.org.springframework=TRACE
The configuration used during development is included here README.md.
Security/CORS:
- The app reads
cors.allowed-originsproperty (inKeycloakResourceConfiguration) for CORS configuration.
OAuth2 Resource Server (JWT):
- Configure issuer/JWK set URIs via Spring properties (e.g.,
spring.security.oauth2.resourceserver.jwt.issuer-uriorspring.security.oauth2.resourceserver.jwt.jwk-set-uri). These are not set inapplication.propertiesby default.
Database:
- Configure
spring.datasource.url,spring.datasource.username,spring.datasource.passwordwhen persisting to PostgreSQL.
Spring Boot supports relaxed binding for environment variables. Use uppercase with underscores. Examples:
SERVER_PORT(maps toserver.port)KEYCLOAK_AUTH_SERVER_URLKEYCLOAK_REALMSPRING_DATASOURCE_URL(maps tospring.datasource.url)SPRING_DATASOURCE_USERNAMESPRING_DATASOURCE_PASSWORDSPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URISPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI
Tests requires the same variables as the application, plus OAuth2 client credentials to obtain a token from Keycloak. Example values for local testing:
OAUTH2_CLIENT_ID(api-client)OAUTH2_REALM(development)OAUTH2_URL(http://localhost:8080)OAUTH2_CLIENT_SECRET(password)OAUTH2_CLIENT_USERNAME(api.user@development.com)OAUTH2_GRANT_TYPE(password)
All endpoints require an Authorization: Bearer <access_token> header.
GET /api/users— returns users in this serviceGET /api/users/{id}— returns a user by internal idGET /api/keycloak/users— returns users from KeycloakGET /api/keycloak/users/{id}— returns a Keycloak user by idGET /api/keycloak/clients— returns Keycloak clientsGET /api/keycloak/clients/{id}— returns a Keycloak client by idGET /api/keycloak/roles— returns Keycloak rolesGET /api/keycloak/roles/{id}— returns a Keycloak role by id
See keycloak-resource.http for example requests, including how to obtain a token from Keycloak and invoke these endpoints locally.
- Clone and enter the project
git clone <this-repo-url>
cd keycloak-resource
- Configure environment (pick one):
- via properties: edit
src/main/resources/application.properties - via env vars: export variables as shown above
- Ensure Keycloak is running and configured to issue tokens for this API.
- From
keycloak-resource.http, there are two sample realms shown (developmenton :8080 anddemoon :9090). See TODOs to finalize which realm/client this project uses by default.
- From
Using Maven wrapper (recommended):
./mvnw spring-boot:run
Build a jar and run:
./mvnw -DskipTests package
java -jar target/keycloak-resource-0.0.1-SNAPSHOT.jar
Specify configuration (examples):
export SERVER_PORT=8081
export CORS_ALLOWED_ORIGINS=http://localhost:4200
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/spring-keycloak
export SPRING_DATASOURCE_USERNAME=postgres
export SPRING_DATASOURCE_PASSWORD=password
export SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://127.0.0.1:9090/realms/demo
export SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI=http://127.0.0.1:9090/realms/demo/protocol/openid-connect/certs
./mvnw spring-boot:run
Build image:
./mvnw -DskipTests package
docker build -t keycloak-resource:local .
Run container (example):
docker run --rm -p 8081:8081 \
-e SERVER_PORT=8081 \
-e CORS_ALLOWED_ORIGINS=http://localhost:4200 \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/spring-keycloak \
-e SPRING_DATASOURCE_USERNAME=postgres \
-e SPRING_DATASOURCE_PASSWORD=password \
-e SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://127.0.0.1:9090/realms/demo \
-e SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI=http://127.0.0.1:9090/realms/demo/protocol/openid-connect/certs \
keycloak-resource:local
Note: The provided Dockerfile sets several env vars, but some names use hyphens which do not map as expected in Spring Boot environment binding. See TODOs.
- Build:
./mvnw -DskipTests package - Run:
./mvnw spring-boot:run - Check dependencies tree:
./mvnw dependency:tree - Format (if you use a formatter plugin locally): TODO
This project uses unit/integration tests with Spring Boot.
- Run all tests (includes Failsafe ITs):
./mvnw verify
- Only unit tests (skip integration tests):
./mvnw -DskipITs test
Integration test naming pattern is configured via Failsafe in pom.xml to include **/*IT.java.
keycloak-resource/
├─ Dockerfile
├─ keycloak-resource.http # sample HTTP requests for Keycloak and this API
├─ pom.xml
├─ src/
│ ├─ main/java/com/example/keycloakresource/
│ │ ├─ KeycloakResourceApplication.java # Spring Boot entry point
│ │ ├─ KeycloakResourceConfiguration.java # Security/CORS configuration
│ │ ├─ KeycloakJwtAuthenticationConverter.java # (optional converter, currently not wired)
│ │ ├─ users/ # local user domain (JPA entity, controller/service)
│ │ └─ keycloak/ # DTOs/controllers/services talking to Keycloak
│ └─ main/resources/
│ └─ application.properties
│
│ └─ test/java/com/example/keycloakresource/integration/ # integration tests (*IT.java)
└─ target/...
The pom.xml does not currently declare a specific license.
TODO: Add a license (e.g., MIT/Apache-2.0) and include the license file and metadata in pom.xml.
- Reconcile Java versions: Dockerfile uses Java 21,
pom.xmltargets Java 17. Decide on one and align both. - Fix Dockerfile env var names: replace hyphens with underscores, e.g.,
SPRING_JPA_SHOW-SQL→SPRING_JPA_SHOW_SQLSPRING_JPA_GENERATE-DDL→SPRING_JPA_GENERATE_DDLSPRING_JPA_HIBERNATE_DDL-AUTO→SPRING_JPA_HIBERNATE_DDL_AUTOSPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER-URI→SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URISPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK-SET-URI→SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URIKEYCLOAK_AUTH-SERVER-URL→ (not used directly by Spring; consider removing or mapping to your own property)CORS_ALLOWED-ORIGINS→CORS_ALLOWED_ORIGINS
- Finalize and document the Keycloak realm/client settings used by default (the
.httpfile shows bothdevelopmenton :8080 anddemoon :9090). - Add database migration tool (e.g., Flyway/Liquibase) if schema needs control in non-dev environments.
- Add CI workflow to run
./mvnw verifyon pushes/PRs.