Named after the Roman god of gates, transitions, and beginnings
This is a GitHub template repository! Use it to quickly create your own federated authentication service.
Janus is a production-ready federated authentication service that acts as a gateway between client applications and Keycloak, enriching JWTs with custom user data from a PostgreSQL database. It provides a unified authentication experience by combining Keycloak's identity management with application-specific user attributes.
- Click "Use this template" button above or click here
- Clone your new repository
- Run the initialization script:
./scripts/init-template.sh
- Follow the setup guide: See TEMPLATE_SETUP.md for detailed instructions
- Template Setup Guide - Complete guide to customize this template
- Package Renaming Guide - Detailed package customization instructions
- Stateless REST API: True RESTful design with JWT Bearer token authentication
- Keycloak Integration: Seamless OAuth2/OIDC authentication with Keycloak
- Token Enhancement: Enriches JWTs with custom user attributes from PostgreSQL
- User Management API: Query and manage users via RESTful endpoints
- API Versioning: Package-based versioning with multiple versions coexisting
- SpEL Support: Dynamic claim generation using Spring Expression Language
- Vertical Slice Architecture: Organized by feature for maintainability
- Swagger/OpenAPI: Interactive API documentation with try-it-out functionality
- Database Seeding: Pre-configured sample data for development
- Docker Support: Easy local development with Docker Compose
- Automated Configuration: Zero-config local setup with pre-configured Keycloak realm
- Security First: Built with Spring Security 6.x best practices
Client Application β Janus β Keycloak (Authentication)
β
PostgreSQL (Custom User Attributes)
β
Client Application β Enriched JWT Token
- Spring Boot 4.0.1 - Application framework
- Spring Security 6.x - Security and OAuth2 support
- Keycloak 23.0.4 - Identity and access management
- PostgreSQL 15 - User data storage
- Hibernate 6.x - ORM with optimistic locking
- Lombok - Reduces boilerplate code
- SpEL - Dynamic expression evaluation
- Docker Compose - Local development environment
- SpringDoc OpenAPI 2.3.0 - API documentation (Swagger)
The codebase is organized by feature (vertical slices) with package-based API versioning:
/api/v1- Version 1 API interfaces with Swagger documentation/auth/v1- Version 1 authentication flow implementation/token- Token customization and enhancement (shared across versions)/user- User domain logic and data access (shared across versions)/config- Security and infrastructure configuration/exception- Global error handling
Versioning Strategy: Each API version has its own package (e.g., v1, v2). Controllers in versioned packages implement corresponding API interfaces, enabling multiple API versions to coexist.
- Java 21+
- Docker & Docker Compose
- Maven 3.8+
Start PostgreSQL and Keycloak using Docker Compose:
docker-compose up -dThis will automatically:
- Start PostgreSQL on
localhost:5432 - Create both
janusandkeycloakdatabases - Start Keycloak on
localhost:8080 - Import the pre-configured
janusrealm with:- Client:
janus-client(secret:change-me) - Sample users (admin/admin, john.doe/password, jane.smith/password)
- All required roles
- Client:
Wait for services to be healthy (~60 seconds for Keycloak).
Access Keycloak Admin Console: http://localhost:8080
- Login:
admin/admin - Select the
janusrealm from the dropdown - Verify the
janus-clientexists with the correct settings
Note: The realm, client, and test users are automatically configured via keycloak-realm.json. No manual setup required!
π Learn more: See Automated Setup Documentation for details on configuration files and customization.
# Build the application
mvn clean package
# Run with dev profile (default - detailed logging, no SSL)
mvn spring-boot:run -Dspring-boot.run.profiles=dev
# Run with production profile (secure settings, SSL enabled)
mvn spring-boot:run -Dspring-boot.run.profiles=prod
# Run without specifying profile (defaults to dev)
mvn spring-boot:runThe application will start on http://localhost:9090
# Health check (public endpoint)
curl http://localhost:9090/api/v1/auth/health
# Get access token from Keycloak using pre-configured test user
curl -X POST http://localhost:8080/realms/janus/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=janus-client" \
-d "client_secret=change-me" \
-d "username=john.doe" \
-d "password=password"
# Use the access token to call protected endpoints
curl http://localhost:9090/api/v1/auth/token \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
curl http://localhost:9090/api/v1/auth/user \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"# All user management endpoints require Bearer token authentication
# Get user by Keycloak ID
curl http://localhost:9090/api/v1/users/keycloak/kc-user-001 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Get user by username
curl http://localhost:9090/api/v1/users/username/john.doe \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Check if user exists
curl http://localhost:9090/api/v1/users/exists/kc-user-001 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Deactivate user
curl -X POST http://localhost:9090/api/v1/users/kc-user-001/deactivate \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"janus/
βββ src/main/java/com/dotbrains/janus/
β βββ JanusApplication.java # Main application class
β βββ api/
β β βββ v1/
β β βββ AuthAPI.java # v1 Auth API interface
β β βββ UserAPI.java # v1 User API interface
β βββ auth/
β β βββ v1/
β β βββ AuthController.java # v1 authentication endpoints
β βββ config/
β β βββ SecurityConfig.java # Spring Security configuration
β β βββ DatabaseConfig.java # Database & environment config
β β βββ OpenApiConfig.java # Swagger/OpenAPI configuration
β βββ token/
β β βββ TokenCustomizer.java # JWT enhancement logic
β β βββ CustomClaimsMapper.java # SpEL-based claims mapping
β βββ user/
β β βββ v1/
β β β βββ UserController.java # v1 user management endpoints
β β βββ User.java # User entity
β β βββ UserRole.java # Role entity
β β βββ UserRepository.java # Data access
β β βββ UserService.java # Business logic
β βββ exception/
β βββ GlobalExceptionHandler.java # Error handling
βββ src/main/resources/
β βββ application.yml # Main configuration
β βββ application-dev.yml # Development profile
β βββ application-prod.yml # Production profile
β βββ schema.sql # Database schema
β βββ data.sql # Seed data
βββ docker-compose.yml # Local infrastructure
βββ pom.xml # Maven dependencies
βββ README.md # This file
Janus automatically detects the environment and applies appropriate security settings.
Default - Used when no profile is specified or when explicitly set.
mvn spring-boot:run -Dspring-boot.run.profiles=devFeatures:
- β Detailed logging (DEBUG level)
- β SQL queries logged
- β Swagger UI enabled
- β Database seeding enabled
- β DevTools hot-reload
β οΈ SSL disabled (localhost only)β οΈ Permissive error messages
Secure - Automatically enables production-grade security.
mvn spring-boot:run -Dspring-boot.run.profiles=prodFeatures:
- π SSL/TLS enforced for database
- π HTTPS-only cookies (secure flag)
- π Strict CORS policy
- π Swagger UI disabled
- π Database seeding disabled
- π Minimal logging (WARN level)
- π No error stack traces
- π Connection leak detection
- π Connection validation
The application automatically detects the environment on startup:
========================================
Environment: prod
Production Mode: true
========================================
Production environment detected - enabling SSL for database connections
Environment is determined by:
- Active Spring profiles (
spring.profiles.active) - Checks for
prodorproductionin profile names - Defaults to development if no production profile found
# Set environment variable
export SPRING_PROFILES_ACTIVE=prod
# Or use command line
java -jar janus.jar --spring.profiles.active=prod
# Or with Maven
mvn spring-boot:run -Dspring-boot.run.profiles=prod@Value annotations. See SECURITY.md for detailed security configuration and best practices.
Use environment variables or .env file (copy from .env.example):
export DATABASE_PASSWORD=secure-password
export KEYCLOAK_CLIENT_SECRET=client-secret-from-keycloakJanus is a stateless REST API using JWT Bearer token authentication:
SessionCreationPolicy.STATELESS- No server-side sessions- All requests authenticated via
Authorization: Bearer <token>header - Tokens obtained directly from Keycloak OAuth2 token endpoint
- True RESTful design - fully stateless and scalable
Configure allowed origins in application.yml:
janus:
cors:
allowed-origins: http://localhost:3000,http://localhost:8080
allowed-methods: GET,POST,PUT,DELETE,OPTIONS
allow-credentials: trueConfigure which data to include in enhanced tokens:
janus:
token:
enhancement:
enabled: true
include-user-roles: true
include-user-attributes: trueThe database includes:
users- User profile informationuser_roles- User role assignments
Proper indexing is implemented to prevent deadlocks:
idx_users_keycloak_ididx_users_usernameidx_users_emailidx_users_employee_id
Sample users are pre-loaded:
john.doe- Senior Software Engineerjane.smith- Product Managerbob.johnson- DevOps Engineeralice.williams- UX Designercharlie.brown- Junior Developerdiana.prince- Security Architectadmin.user- System Administrator
- Start services:
docker-compose up -d - Run application:
mvn spring-boot:run -Dspring-boot.run.profiles=dev - Navigate to: http://localhost:9090/oauth2/authorization/keycloak
- Login with Keycloak credentials
- Check enhanced token: http://localhost:9090/api/v1/auth/token
mvn testJanus includes comprehensive interactive API documentation powered by Swagger/OpenAPI 3.0.
Access Swagger UI: http://localhost:9090/swagger-ui.html
The Swagger interface provides:
- Interactive API testing
- Complete request/response examples
- Authentication flow documentation
- Schema definitions
- Try-it-out functionality
GET /oauth2/authorization/keycloak- Initiate OAuth2 loginGET /api/v1/auth/success- OAuth2 login success callbackGET /api/v1/auth/failure- OAuth2 login failure callbackGET /api/v1/auth/user- Get current user with enhanced claimsGET /api/v1/auth/token- Get enhanced JWT tokenGET /api/v1/auth/health- Health check
GET /api/v1/users/keycloak/{keycloakId}- Get user by Keycloak IDGET /api/v1/users/username/{username}- Get user by username with rolesGET /api/v1/users/exists/{keycloakId}- Check if user existsPOST /api/v1/users/{keycloakId}/deactivate- Deactivate user account
GET /swagger-ui.html- Swagger UI interfaceGET /v3/api-docs- OpenAPI 3.0 JSON specification
GET /- Root endpointGET /actuator/health- Application health
Janus uses URL-based API versioning to provide a clear and explicit API contract. This ensures backward compatibility and allows for future API evolution without breaking existing clients.
All API endpoints are prefixed with /api/v1:
/api/v1/auth/* # Authentication endpoints
/api/v1/users/* # User management endpoints
- Package-based organization: Each version has its own package (
api/v1,auth/v1, etc.) - URL-based API paths: Explicit version in the URL path (
/api/v1/auth) - Interface-driven: API interfaces define version-specific contracts
- Stable contracts: v1 endpoints remain stable even when v2 is added
- Coexistence: Multiple versions can run simultaneously
- Documentation: Each version has separate Swagger documentation
To add version 2 of the API:
- Create new packages:
api/v2,auth/v2, anduser/v2 - Create API interfaces:
api/v2/AuthAPI.javawith@RequestMapping("/api/v2/auth")api/v2/UserAPI.javawith@RequestMapping("/api/v2/users")
- Create controllers:
auth/v2/AuthController.javaimplementingapi/v2/AuthAPIuser/v2/UserController.javaimplementingapi/v2/UserAPI
- Update SecurityConfig to permit v2 endpoints
- Both v1 and v2 will coexist and be served simultaneously
# v1 Authentication endpoints
curl http://localhost:9090/api/v1/auth/health
curl http://localhost:9090/api/v1/auth/user
curl http://localhost:9090/api/v1/auth/token
# v1 User Management endpoints
curl http://localhost:9090/api/v1/users/keycloak/kc-user-001
curl http://localhost:9090/api/v1/users/username/john.doe
curl http://localhost:9090/api/v1/users/exists/kc-user-001- v1.0 (Current) - Initial release with OAuth2/Keycloak integration and User Management
- Authentication Endpoints:
/api/v1/auth/success- OAuth2 login success callback/api/v1/auth/failure- OAuth2 login failure callback/api/v1/auth/user- Get current authenticated user/api/v1/auth/token- Get enhanced JWT token/api/v1/auth/health- Health check endpoint
- User Management Endpoints:
/api/v1/users/keycloak/{keycloakId}- Get user by Keycloak ID/api/v1/users/username/{username}- Get user by username/api/v1/users/exists/{keycloakId}- Check user existence/api/v1/users/{keycloakId}/deactivate- Deactivate user account
- Authentication Endpoints:
| Variable | Description | Default |
|---|---|---|
SERVER_PORT |
Application port | 9090 |
KEYCLOAK_CLIENT_ID |
Keycloak client ID | janus-client |
KEYCLOAK_CLIENT_SECRET |
Keycloak client secret | change-me |
KEYCLOAK_ISSUER_URI |
Keycloak issuer URI | http://localhost:8080/realms/janus |
POSTGRES_DB |
Database name | janus |
POSTGRES_USER |
Database user | janus |
POSTGRES_PASSWORD |
Database password | janus123 |
Key configuration sections in application.yml:
- Spring Security OAuth2 client
- PostgreSQL datasource with HikariCP
- Hibernate/JPA configuration
- Janus-specific settings (CORS, token enhancement)
Issue: Row-level deadlocks on users table
Solution:
- Proper indexing on frequently queried columns
- READ_COMMITTED isolation level
- Optimistic locking with
@Version
Issue: User data not refreshed after database updates
Solution: Session policy allows cache, but user sync logic updates data on each login if needed.
Janus uses Spring Expression Language (SpEL) for dynamic claim generation:
// Full name expression
Expression fullNameExpression = spelParser.parseExpression("firstName + ' ' + lastName");
// Admin role check
Expression hasAdminRoleExpression = spelParser.parseExpression("roleNames.contains('ADMIN')");
// Active status
Expression isActiveExpression = spelParser.parseExpression("isActive == true");Comprehensive documentation is available in the docs/ directory:
- Security Guide - Complete security guide covering credentials, JWT authentication, Keycloak setup, SSL/TLS, and incident response
- API Documentation - Swagger/OpenAPI setup and interface pattern for clean API design
- API Versioning Guide - Package-based versioning strategy with examples
- Documentation Index - Complete documentation overview and quick links
- Fork the repository
- Create a feature branch
- Follow the vertical slice architecture
- Add tests for new functionality
- Submit a pull request
This project is licensed under the MIT License.
- Inspired by the original FAS (Federated Authentication Service)
- Named after Janus, the Roman god of gates and transitions
- Built with Spring Boot and Keycloak best practices
For issues and questions, please open a GitHub issue.
Janus - The gateway to your applications πͺβ¨