| name | judo-runtime:authentication-flow |
| description | Comprehensive guide to the JUDO authentication pipeline. Use when you need to understand how authentication works, debug auth issues, configure OAuth2/OpenID Connect, or integrate with identity providers like Keycloak. |
| metadata | {"author":"BlackBelt Technology","version":"${project.version}"} |
JUDO Authentication Flow
Complete guide to understanding the authentication pipeline in JUDO Runtime Core.
Overview
JUDO Security provides a multi-layer authentication system supporting:
- OAuth2 / OpenID Connect (OIDC) authentication
- JWT token validation and claims extraction
- Multi-tenant / multi-realm support
- Actor-based access control
- User lifecycle management
Architecture
graph TB
subgraph "Client"
A[HTTP Request]
end
subgraph "CXF Layer"
B[KeycloakLoginInterceptor]
end
subgraph "Security Core"
C[RealmExtractor]
D[OpenIdConfigurationProvider]
E[UserManager]
end
subgraph "Identity Provider"
F[Keycloak Server]
end
subgraph "Application"
G[Business Logic]
H[DAO Layer]
end
A -->|Authorization: Bearer token| B
B --> C
C -->|Extract actor type| B
B --> D
D -->|Get OIDC config| F
B -->|Validate JWT| F
B -->|Set SecurityContext| G
G --> H
H --> E
E -->|Sync users| F
Authentication Sequence
sequenceDiagram
participant Client
participant Interceptor as KeycloakLoginInterceptor
participant Extractor as RealmExtractor
participant OIDC as OpenIdConfigurationProvider
participant Keycloak
participant App as Application
Client->>Interceptor: HTTP Request + Bearer Token
Interceptor->>Extractor: extractActorType(request)
Extractor-->>Interceptor: Optional<EClass> actorType
alt Actor Type Found
Interceptor->>OIDC: getOpenIdConfiguration(actorType)
OIDC-->>Interceptor: OIDC Config (issuer, jwks_uri, etc.)
Interceptor->>Keycloak: Validate JWT using JWKS
Keycloak-->>Interceptor: Token validation result
alt Valid Token
Interceptor->>Interceptor: Extract claims, map to attributes
Interceptor->>App: SecurityContext with JudoPrincipal
App-->>Client: Response
else Invalid Token
Interceptor-->>Client: 401 Unauthorized
end
else Anonymous Access
Interceptor->>App: No SecurityContext
App-->>Client: Response (if allowed)
end
Core Interfaces
OpenIdConfigurationProvider
Provides OpenID Connect configuration for each actor type:
public interface OpenIdConfigurationProvider {
String getOpenIdConfigurationUrl(EClass actorType);
Map<String, Object> getOpenIdConfiguration(EClass actorType);
String getServerUrl();
String getClientId(EClass actorType);
void ping();
}
RealmExtractor
Extracts actor type from HTTP requests:
public interface RealmExtractor {
Optional<EClass> extractActorType(HttpServletRequest request);
}
Default Implementation: PathInfoRealmExtractor
Extracts actor from URL path:
- Request:
POST /api/Model/Actor/operation
- Extracted:
Model.Actor actor type
UserManager
Manages users in the identity provider:
public interface UserManager<ID> {
Optional<Map<String, Object>> getUser(EClass actor, ID username);
List<Map<String, Object>> getAllUsers(EClass actor);
void createUser(EClass actor, Map<String, Object> user);
void updateUser(EClass actor, ID username, Map<String, Object> user);
void deleteUser(EClass actor, ID username);
Optional<EClass> getManagedActorOfPrincipal(EClass principalType);
Map<String, String> getPrincipalAttributeMapping(EClass principalType);
Optional<ID> getUsername(EClass principalType, Map<String, Object> user);
}
PasswordPolicy
Defines password generation rules:
public interface PasswordPolicy<ID> extends Function<Map<String, Object>, Optional<ID>> {
}
Implementations:
NoPasswordPolicy - No automatic password generation
SameUsernamePasswordPolicy - Password equals username
SameEmailPasswordPolicy - Password equals email
Token Flow
graph LR
subgraph "JWT Token"
A[Header]
B[Payload/Claims]
C[Signature]
end
subgraph "Claim Mapping"
D[preferred_username]
E[email]
F[custom claims]
end
subgraph "JudoPrincipal"
G[name]
H[realm]
I[client]
J[attributes]
end
B --> D
B --> E
B --> F
D --> G
D --> J
E --> J
F --> J
User Synchronization
The UserManagedWrappedDao automatically syncs users with the identity provider:
sequenceDiagram
participant App as Application
participant Wrapper as UserManagedWrappedDao
participant DAO as Delegate DAO
participant UserMgr as UserManager
participant Keycloak
App->>Wrapper: create(Actor, payload)
Wrapper->>DAO: create(Actor, payload)
DAO-->>Wrapper: Created entity
Wrapper->>UserMgr: createUser(actor, userData)
UserMgr->>Keycloak: POST /admin/realms/{realm}/users
Keycloak-->>UserMgr: 201 Created
Wrapper-->>App: Created entity
Note over Wrapper,UserMgr: Same flow for update/delete
Configuration
Keycloak Module Configuration (Guice)
JudoKeycloakModuleConfiguration.builder()
.keycloakServerUrl("http://localhost:8080/auth")
.keycloakExternalUrl("https://auth.example.com")
.keycloakAdminUser("admin")
.keycloakAdminPassword("admin")
.keycloakClientSecret(null)
.keycloakUserManagerEnabled(true)
.keycloakUpdateExistingUsers(false)
.keycloakAsyncServiceCall(true)
.keycloakRetryMaxAttempts(1000)
.keycloakRetryWaitDuration(1000L)
.keycloakRetryExponentialBackoff(true)
.build();
Actor Type Annotations
Actor types require these annotations in the ASM model:
@actorType(managed=true) // Enable user management
@realm("my-realm") // Keycloak realm name
@claim("USERNAME") // Attribute mapped to Keycloak username
@claim("EMAIL") // Attribute mapped to Keycloak email
Acceptable Clients Whitelist
By default, the JWT token's azp (authorized party) claim must match an actor type FQN in the ASM model.
The acceptableClients parameter allows additional Keycloak client IDs to authenticate against specific actor types.
Configuration
Format: ActorTypeFQN=client1,client2;OtherActorFQN=client3
Client IDs can use either dashes or dots — dashes are automatically converted to dots to match the internal convertClientToActorName() normalization.
Platform (OSGi):
JUDO_PLATFORM_ACCEPTABLE_CLIENTS=MyModel.UserActor=frontend-app,mobile-app;MyModel.AdminActor=admin-tool
Spring Boot (application.properties):
judo.actorResolver.acceptableClients=MyModel.UserActor=frontend-app,mobile-app;MyModel.AdminActor=admin-tool
Guice (JudoDefaultModuleConfiguration):
JudoDefaultModuleConfiguration.builder()
.actorResolverAcceptableClients("MyModel.UserActor=frontend-app,mobile-app")
.build();
How It Works
1. Token arrives with azp = "frontend-app"
2. KeycloakLoginInterceptor converts to "frontend.app" (dashes → dots)
3. DefaultActorResolver.authenticateByPrincipal() tries:
a. asmUtils.resolve("frontend.app") → empty (not an actor FQN)
b. Look up "frontend.app" in acceptableClients map → found under "MyModel.UserActor"
c. asmUtils.resolve("MyModel.UserActor") → EClass ✓
d. Proceed with MyModel.UserActor as the actor type
Security Constraints
- Token verification (signature, expiry, audience) is unchanged — always validated by Keycloak first
- Each client is mapped to exactly one actor type — prevents privilege escalation
- Duplicate client across actor types is rejected at startup with
IllegalArgumentException
- Empty configuration = existing behavior unchanged (backward compatible)
Security Context
After successful authentication, JudoPrincipal is available:
@Context
SecurityContext securityContext;
public Response myOperation() {
JudoPrincipal principal = (JudoPrincipal) securityContext.getUserPrincipal();
String username = principal.getName();
String realm = principal.getRealm();
String client = principal.getClient();
Map<String, Object> claims = principal.getAttributes();
}
Error Handling
| Error | HTTP Status | Description |
|---|
| Missing token | Depends on endpoint | Anonymous access (may be allowed) |
| Expired token | 401 | ACCESS_TOKEN_EXPIRED error code |
| Invalid signature | 401 | Token verification failed |
| Unknown realm | N/A | Request processed as anonymous |
Debugging
Enable Debug Logging
<logger name="hu.blackbelt.judo.runtime.core.security" level="DEBUG"/>
<logger name="org.keycloak" level="DEBUG"/>
Common Issues
-
Token validation fails
- Check Keycloak server URL matches issuer in token
- Verify JWKS endpoint is accessible
- Check clock skew between servers
-
User not created in Keycloak
- Verify
keycloakUserManagerEnabled=true
- Check actor type has
@actorType(managed=true)
- Review Keycloak admin credentials
-
Claims not mapped
- Verify
@claim annotations on actor attributes
- Check
AttributeBinding in Keycloak model
- Enable DEBUG logging to see token contents
See Also
judo-runtime-core-security-keycloak - Keycloak implementation
judo-runtime-core-security-keycloak-cxf - CXF interceptors
judo-runtime-core-guice-keycloak - Guice configuration
judo-runtime-core-accessmanager-api - Authorization APIs