Keycloak
Since Camel 4.15
Both producer and consumer are supported
The Keycloak component supports running operations on Keycloak instance and policy enforcements.
Component Features
The Keycloak component provides two main functionalities:
-
Producer Operations - Manage Keycloak instances via the Admin API (realms, users, roles, clients)
-
Security Policies - Route-level authorization using Keycloak authentication and authorization services
URI Format
keycloak://label[?options]
You can append query options to the URI in the following format:
?options=value&option2=value&…
Configuring Options
Camel components are configured on two separate levels:
-
component level
-
endpoint level
Configuring Component Options
At the component level, you set general and shared configurations that are, then, inherited by the endpoints. It is the highest configuration level.
For example, a component may have security settings, credentials for authentication, urls for network connection and so forth.
Some components only have a few options, and others may have many. Because components typically have pre-configured defaults that are commonly used, then you may often only need to configure a few options on a component; or none at all.
You can configure components using:
-
the Component DSL.
-
in a configuration file (
application.properties,*.yamlfiles, etc). -
directly in the Java code.
Configuring Endpoint Options
You usually spend more time setting up endpoints because they have many options. These options help you customize what you want the endpoint to do. The options are also categorized into whether the endpoint is used as a consumer (from), as a producer (to), or both.
Configuring endpoints is most often done directly in the endpoint URI as path and query parameters. You can also use the Endpoint DSL and DataFormat DSL as a type safe way of configuring endpoints and data formats in Java.
A good practice when configuring options is to use Property Placeholders.
Property placeholders provide a few benefits:
-
They help prevent using hardcoded urls, port numbers, sensitive information, and other settings.
-
They allow externalizing the configuration from the code.
-
They help the code to become more flexible and reusable.
The following two sections list all the options, firstly for the component followed by the endpoint.
Component Options
The Keycloak component supports 33 options, which are listed below.
| Name | Description | Default | Type |
|---|---|---|---|
Pre-obtained access token for authentication. When provided, this token will be used directly instead of obtaining one through username/password or client credentials flow. | String | ||
Filter admin events by authentication client ID. | String | ||
Filter admin events by authentication IP address. | String | ||
Keycloak realm to authenticate against. If not specified, the realm parameter is used for authentication. This is useful when you want to authenticate against one realm (e.g., master) but perform operations on another realm. | master | String | |
Filter admin events by authentication realm. | String | ||
Filter admin events by authentication user ID. | String | ||
Filter events by client ID. | String | ||
Keycloak client ID. | String | ||
Keycloak client secret. | String | ||
Component configuration. | KeycloakConfiguration | ||
Filter events by start date/time in milliseconds since epoch. | String | ||
Filter events by end date/time in milliseconds since epoch. | String | ||
Type of events to consume: events or admin-events. | events | String | |
Offset for pagination (first result index). | 0 | int | |
Enable caching of token introspection results to reduce API calls to Keycloak. | true | boolean | |
Time-to-live for cached introspection results in seconds. | 60 | long | |
Filter events by IP address. | String | ||
Autowired To use an existing configured Keycloak admin client. | Keycloak | ||
Maximum number of events to retrieve per poll. | 100 | int | |
The operation to perform. Enum values:
| KeycloakOperations | ||
Filter admin events by operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE). | String | ||
Keycloak password. | String | ||
If we want to use a POJO request as body or not. | false | boolean | |
Keycloak realm, the default is master because usually all the operations are done starting from the master realm. | master | String | |
Filter admin events by resource path. | String | ||
Keycloak server URL. | String | ||
Filter events by event types (comma-separated list, e.g., LOGIN,LOGOUT). | String | ||
Filter events by user ID. | String | ||
Keycloak username. | String | ||
Enable OAuth 2.0 token introspection for real-time token validation. When enabled, tokens are validated by calling Keycloak’s introspection endpoint instead of local JWT parsing. This allows detecting revoked tokens before expiration. | false | boolean | |
Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored. | false | boolean | |
Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel’s routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing. | false | boolean | |
Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc. | true | boolean |
Endpoint Options
The Keycloak endpoint is configured using URI syntax:
keycloak:label
With the following path and query parameters:
Query Parameters (49 parameters)
| Name | Description | Default | Type |
|---|---|---|---|
Pre-obtained access token for authentication. When provided, this token will be used directly instead of obtaining one through username/password or client credentials flow. | String | ||
Filter admin events by authentication client ID. | String | ||
Filter admin events by authentication IP address. | String | ||
Keycloak realm to authenticate against. If not specified, the realm parameter is used for authentication. This is useful when you want to authenticate against one realm (e.g., master) but perform operations on another realm. | master | String | |
Filter admin events by authentication realm. | String | ||
Filter admin events by authentication user ID. | String | ||
Filter events by client ID. | String | ||
Keycloak client ID. | String | ||
Keycloak client secret. | String | ||
Filter events by start date/time in milliseconds since epoch. | String | ||
Filter events by end date/time in milliseconds since epoch. | String | ||
Type of events to consume: events or admin-events. | events | String | |
Offset for pagination (first result index). | 0 | int | |
Enable caching of token introspection results to reduce API calls to Keycloak. | true | boolean | |
Time-to-live for cached introspection results in seconds. | 60 | long | |
Filter events by IP address. | String | ||
Autowired To use an existing configured Keycloak admin client. | Keycloak | ||
Maximum number of events to retrieve per poll. | 100 | int | |
The operation to perform. Enum values:
| KeycloakOperations | ||
Filter admin events by operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE). | String | ||
Keycloak password. | String | ||
If we want to use a POJO request as body or not. | false | boolean | |
Keycloak realm, the default is master because usually all the operations are done starting from the master realm. | master | String | |
Filter admin events by resource path. | String | ||
Keycloak server URL. | String | ||
Filter events by event types (comma-separated list, e.g., LOGIN,LOGOUT). | String | ||
Filter events by user ID. | String | ||
Keycloak username. | String | ||
Enable OAuth 2.0 token introspection for real-time token validation. When enabled, tokens are validated by calling Keycloak’s introspection endpoint instead of local JWT parsing. This allows detecting revoked tokens before expiration. | false | boolean | |
If the polling consumer did not poll any files, you can enable this option to send an empty message (no body) instead. | false | boolean | |
Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored. | false | boolean | |
To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored. | ExceptionHandler | ||
Sets the exchange pattern when the consumer creates an exchange. Enum values:
| ExchangePattern | ||
A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation to control error handling usually occurred during the poll operation before an Exchange have been created and being routed in Camel. | PollingConsumerPollStrategy | ||
Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel’s routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing. | false | boolean | |
The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in. | int | ||
The number of subsequent idle polls that should happen before the backoffMultipler should kick-in. | int | ||
To let the scheduled polling consumer backoff if there has been a number of subsequent idles/errors in a row. The multiplier is then the number of polls that will be skipped before the next actual attempt is happening again. When this option is in use then backoffIdleThreshold and/or backoffErrorThreshold must also be configured. | int | ||
Milliseconds before the next poll. | 500 | long | |
If greedy is enabled, then the ScheduledPollConsumer will run immediately again, if the previous run polled 1 or more messages. | false | boolean | |
Milliseconds before the first poll starts. | 1000 | long | |
Specifies a maximum limit of number of fires. So if you set it to 1, the scheduler will only fire once. If you set it to 5, it will only fire five times. A value of zero or negative means fire forever. | 0 | long | |
The consumer logs a start/complete log line when it polls. This option allows you to configure the logging level for that. Enum values:
| TRACE | LoggingLevel | |
Allows for configuring a custom/shared thread pool to use for the consumer. By default each consumer has its own single threaded thread pool. | ScheduledExecutorService | ||
To use a cron scheduler from either camel-spring or camel-quartz component. Use value spring or quartz for built in scheduler. | none | Object | |
To configure additional properties when using a custom scheduler or any of the Quartz, Spring based scheduler. This is a multi-value option with prefix: scheduler. | Map | ||
Whether the scheduler should be auto started. | true | boolean | |
Time unit for initialDelay and delay options. Enum values:
| MILLISECONDS | TimeUnit | |
Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details. | true | boolean |
Producer Operations
The Keycloak producer supports administrative operations on Keycloak instances via the Admin API.
Configuration
The Keycloak component supports four authentication methods:
-
Access Token (Bearer Token) - Use a pre-obtained access token
-
Refresh Token - Maintain long-running sessions with automatic token refresh
-
Username/Password - Resource Owner Password Credentials flow
-
Client Credentials - Service-to-service authentication
Access Token Authentication
Use this when you have a pre-obtained access token from an external authentication system:
-
Java
// Configure Keycloak component with access token
KeycloakComponent keycloak = context.getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master");
config.setAccessToken("eyJhbGciOiJSUzI1NiIsInR5cC...");
keycloak.setConfiguration(config); Refresh Token Authentication
Use this for long-running sessions that need to maintain authentication without storing credentials. The refresh token will be used to automatically obtain new access tokens when needed:
-
Java
// Configure Keycloak component with refresh token
KeycloakComponent keycloak = context.getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master");
config.setClientId("my-client");
config.setRefreshToken("eyJhbGciOiJIUzI1NiIsInR5cCIgOi...");
// Optional: set client secret for confidential clients
config.setClientSecret("my-client-secret");
keycloak.setConfiguration(config); Refresh token authentication requires a clientId. The clientSecret is optional and should be provided only if your client is configured as confidential in Keycloak. |
Username/Password Authentication
Use this for admin user authentication:
-
Java
// Configure Keycloak component
KeycloakComponent keycloak = context.getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master");
config.setUsername("admin");
config.setPassword("admin");
keycloak.setConfiguration(config); Client Credentials Authentication
Use this for service-to-service authentication:
-
Java
// Configure Keycloak component with client credentials
KeycloakComponent keycloak = context.getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master");
config.setClientId("my-service-client");
config.setClientSecret("my-client-secret");
keycloak.setConfiguration(config); Supported Operations
The component supports the following operations:
-
Realm Management:
createRealm,getRealm,updateRealm,deleteRealm -
User Management:
createUser,getUser,updateUser,listUsers,searchUsers,deleteUser -
User Attributes:
getUserAttributes,setUserAttribute,deleteUserAttribute -
User Credentials:
getUserCredentials,deleteUserCredential -
User Actions:
sendVerifyEmail,sendPasswordResetEmail,addRequiredAction,removeRequiredAction,executeActionsEmail -
Role Management:
createRole,getRole,updateRole,listRoles,deleteRole,assignRoleToUser,removeRoleFromUser,getUserRoles -
Group Management:
createGroup,getGroup,updateGroup,listGroups,deleteGroup,addUserToGroup,removeUserFromGroup,listUserGroups -
Client Management:
createClient,getClient,updateClient,listClients,deleteClient -
Client Secret Management:
getClientSecret,regenerateClientSecret -
Client Role Management:
createClientRole,getClientRole,updateClientRole,listClientRoles,deleteClientRole,assignClientRoleToUser,removeClientRoleFromUser -
Password Management:
resetUserPassword -
Session Management:
listUserSessions,logoutUser -
Client Scope Management:
createClientScope,getClientScope,updateClientScope,listClientScopes,deleteClientScope -
Identity Provider Management:
createIdentityProvider,getIdentityProvider,updateIdentityProvider,listIdentityProviders,deleteIdentityProvider -
Authorization Services:
createResource,getResource,updateResource,listResources,deleteResource,createResourcePolicy,getResourcePolicy,updateResourcePolicy,listResourcePolicies,deleteResourcePolicy,createResourcePermission,getResourcePermission,updateResourcePermission,listResourcePermissions,deleteResourcePermission,evaluatePermission
Realm Operations
-
Java
// Create a new realm
template.sendBodyAndHeader("keycloak:admin?operation=createRealm", null,
KeycloakConstants.REALM_NAME, "my-new-realm");
// Get realm information
template.sendBodyAndHeader("keycloak:admin?operation=getRealm", null,
KeycloakConstants.REALM_NAME, "my-realm");
// Delete a realm
template.sendBodyAndHeader("keycloak:admin?operation=deleteRealm", null,
KeycloakConstants.REALM_NAME, "my-old-realm"); User Operations
-
Java
// Create a new user
Map<String, Object> headers = new HashMap<>();
headers.put(KeycloakConstants.REALM_NAME, "my-realm");
headers.put(KeycloakConstants.USERNAME, "john.doe");
headers.put(KeycloakConstants.USER_EMAIL, "john.doe@example.com");
headers.put(KeycloakConstants.USER_FIRST_NAME, "John");
headers.put(KeycloakConstants.USER_LAST_NAME, "Doe");
template.sendBodyAndHeaders("keycloak:admin?operation=createUser", null, headers);
// Set user password
Map<String, Object> passwordHeaders = new HashMap<>();
passwordHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
passwordHeaders.put(KeycloakConstants.USERNAME, "john.doe");
passwordHeaders.put("CamelKeycloakUserPassword", "secure-password");
passwordHeaders.put("CamelKeycloakUserPasswordTemporary", false);
template.sendBodyAndHeaders("keycloak:admin?operation=setUserPassword", null, passwordHeaders);
// List all users in realm
template.sendBodyAndHeader("keycloak:admin?operation=listUsers", null,
KeycloakConstants.REALM_NAME, "my-realm");
// Delete a user
Map<String, Object> deleteHeaders = new HashMap<>();
deleteHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
deleteHeaders.put(KeycloakConstants.USERNAME, "john.doe");
template.sendBodyAndHeaders("keycloak:admin?operation=deleteUser", null, deleteHeaders); Role Operations
-
Java
-
YAML
// Create a new role
Map<String, Object> roleHeaders = new HashMap<>();
roleHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
roleHeaders.put(KeycloakConstants.ROLE_NAME, "manager");
roleHeaders.put(KeycloakConstants.ROLE_DESCRIPTION, "Manager role with elevated privileges");
template.sendBodyAndHeaders("keycloak:admin?operation=createRole", null, roleHeaders);
// Get role information
Map<String, Object> getRoleHeaders = new HashMap<>();
getRoleHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
getRoleHeaders.put(KeycloakConstants.ROLE_NAME, "manager");
template.sendBodyAndHeaders("keycloak:admin?operation=getRole", null, getRoleHeaders);
// Assign role to user
Map<String, Object> assignHeaders = new HashMap<>();
assignHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
assignHeaders.put(KeycloakConstants.USERNAME, "john.doe");
assignHeaders.put(KeycloakConstants.ROLE_NAME, "manager");
template.sendBodyAndHeaders("keycloak:admin?operation=assignRoleToUser", null, assignHeaders);
// Delete a role
Map<String, Object> deleteRoleHeaders = new HashMap<>();
deleteRoleHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
deleteRoleHeaders.put(KeycloakConstants.ROLE_NAME, "old-role");
template.sendBodyAndHeaders("keycloak:admin?operation=deleteRole", null, deleteRoleHeaders); # Create role route
- route:
from:
uri: direct:create-role
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakRoleName
simple: "${body[roleName]}"
- setHeader:
name: CamelKeycloakRoleDescription
simple: "${body[description]}"
- to:
uri: keycloak:admin?operation=createRole
- log: "Created role: ${header.CamelKeycloakRoleName}"
# Get role route
- route:
from:
uri: direct:get-role
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakRoleName
simple: "${body[roleName]}"
- to:
uri: keycloak:admin?operation=getRole
- log: "Role info: ${body}"
# Assign role to user route
- route:
from:
uri: direct:assign-role
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUsername
simple: "${body[username]}"
- setHeader:
name: CamelKeycloakRoleName
simple: "${body[roleName]}"
- to:
uri: keycloak:admin?operation=assignRoleToUser
- log: "Assigned role ${header.CamelKeycloakRoleName} to user ${header.CamelKeycloakUsername}"
# Delete role route
- route:
from:
uri: direct:delete-role
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakRoleName
simple: "${body[roleName]}"
- to:
uri: keycloak:admin?operation=deleteRole
- log: "Deleted role: ${header.CamelKeycloakRoleName}" Client Operations
-
Java
-
YAML
// Create a new client
Map<String, Object> clientHeaders = new HashMap<>();
clientHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
clientHeaders.put("CamelKeycloakClientId", "my-service-client");
clientHeaders.put("CamelKeycloakClientSecretRequired", true);
clientHeaders.put("CamelKeycloakClientDirectAccessGrantsEnabled", true);
template.sendBodyAndHeaders("keycloak:admin?operation=createClient", null, clientHeaders);
// Get client information
Map<String, Object> getClientHeaders = new HashMap<>();
getClientHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
getClientHeaders.put("CamelKeycloakClientId", "my-service-client");
template.sendBodyAndHeaders("keycloak:admin?operation=getClient", null, getClientHeaders);
// Get client secret
Map<String, Object> secretHeaders = new HashMap<>();
secretHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
secretHeaders.put("CamelKeycloakClientId", "my-service-client");
String clientSecret = template.requestBodyAndHeaders("keycloak:admin?operation=getClientSecret",
null, secretHeaders, String.class);
// Delete a client
Map<String, Object> deleteClientHeaders = new HashMap<>();
deleteClientHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
deleteClientHeaders.put("CamelKeycloakClientId", "old-client");
template.sendBodyAndHeaders("keycloak:admin?operation=deleteClient", null, deleteClientHeaders); # Create client route
- route:
from:
uri: direct:create-client
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientId
simple: "${body[clientId]}"
- setHeader:
name: CamelKeycloakClientSecretRequired
constant: true
- setHeader:
name: CamelKeycloakClientDirectAccessGrantsEnabled
constant: true
- to:
uri: keycloak:admin?operation=createClient
- log: "Created client: ${header.CamelKeycloakClientId}"
# Get client route
- route:
from:
uri: direct:get-client
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientId
simple: "${body[clientId]}"
- to:
uri: keycloak:admin?operation=getClient
- log: "Client info: ${body}"
# Get client secret route
- route:
from:
uri: direct:get-client-secret
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientId
simple: "${body[clientId]}"
- to:
uri: keycloak:admin?operation=getClientSecret
- log: "Client secret retrieved for: ${header.CamelKeycloakClientId}"
# Delete client route
- route:
from:
uri: direct:delete-client
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientId
simple: "${body[clientId]}"
- to:
uri: keycloak:admin?operation=deleteClient
- log: "Deleted client: ${header.CamelKeycloakClientId}" Group Operations
-
Java
-
YAML
// Create a new group
Map<String, Object> groupHeaders = new HashMap<>();
groupHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
groupHeaders.put(KeycloakConstants.GROUP_NAME, "developers");
template.sendBodyAndHeaders("keycloak:admin?operation=createGroup", null, groupHeaders);
// List all groups
template.sendBodyAndHeader("keycloak:admin?operation=listGroups", null,
KeycloakConstants.REALM_NAME, "my-realm");
// Add user to group
Map<String, Object> addToGroupHeaders = new HashMap<>();
addToGroupHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
addToGroupHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
addToGroupHeaders.put(KeycloakConstants.GROUP_ID, "group-id-456");
template.sendBodyAndHeaders("keycloak:admin?operation=addUserToGroup", null, addToGroupHeaders);
// List user's groups
Map<String, Object> userGroupsHeaders = new HashMap<>();
userGroupsHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
userGroupsHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
template.sendBodyAndHeaders("keycloak:admin?operation=listUserGroups", null, userGroupsHeaders);
// Delete a group
Map<String, Object> deleteGroupHeaders = new HashMap<>();
deleteGroupHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
deleteGroupHeaders.put(KeycloakConstants.GROUP_ID, "group-id-456");
template.sendBodyAndHeaders("keycloak:admin?operation=deleteGroup", null, deleteGroupHeaders); # Create group route
- route:
from:
uri: direct:create-group
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakGroupName
simple: "${body[groupName]}"
- to:
uri: keycloak:admin?operation=createGroup
- log: "Created group: ${header.CamelKeycloakGroupName}"
# List groups route
- route:
from:
uri: direct:list-groups
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- to:
uri: keycloak:admin?operation=listGroups
- log: "Groups: ${body}"
# Add user to group route
- route:
from:
uri: direct:add-user-to-group
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakGroupId
simple: "${body[groupId]}"
- to:
uri: keycloak:admin?operation=addUserToGroup
- log: "Added user ${header.CamelKeycloakUserId} to group"
# List user groups route
- route:
from:
uri: direct:list-user-groups
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=listUserGroups
- log: "User groups: ${body}" Password Management Operations
-
Java
-
YAML
// Reset user password
Map<String, Object> passwordHeaders = new HashMap<>();
passwordHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
passwordHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
passwordHeaders.put(KeycloakConstants.USER_PASSWORD, "newSecurePassword123!");
passwordHeaders.put(KeycloakConstants.PASSWORD_TEMPORARY, false); // User won't need to change password
template.sendBodyAndHeaders("keycloak:admin?operation=resetUserPassword", null, passwordHeaders);
// Reset with temporary password (user must change on first login)
Map<String, Object> tempPasswordHeaders = new HashMap<>();
tempPasswordHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
tempPasswordHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
tempPasswordHeaders.put(KeycloakConstants.USER_PASSWORD, "tempPassword123");
tempPasswordHeaders.put(KeycloakConstants.PASSWORD_TEMPORARY, true);
template.sendBodyAndHeaders("keycloak:admin?operation=resetUserPassword", null, tempPasswordHeaders); # Reset user password route
- route:
from:
uri: direct:reset-password
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakUserPassword
simple: "${body[password]}"
- setHeader:
name: CamelKeycloakPasswordTemporary
simple: "${body[temporary]}"
- to:
uri: keycloak:admin?operation=resetUserPassword
- log: "Password reset for user ${header.CamelKeycloakUserId}"
# Reset with temporary password
- route:
from:
uri: direct:reset-temp-password
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakUserPassword
simple: "${body[password]}"
- setHeader:
name: CamelKeycloakPasswordTemporary
constant: true
- to:
uri: keycloak:admin?operation=resetUserPassword
- log: "Temporary password set for user ${header.CamelKeycloakUserId}" User Search Operations
-
Java
-
YAML
// Search users by query
Map<String, Object> searchHeaders = new HashMap<>();
searchHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
searchHeaders.put(KeycloakConstants.SEARCH_QUERY, "john");
template.sendBodyAndHeaders("keycloak:admin?operation=searchUsers", null, searchHeaders);
// Search with pagination
Map<String, Object> paginatedSearchHeaders = new HashMap<>();
paginatedSearchHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
paginatedSearchHeaders.put(KeycloakConstants.SEARCH_QUERY, "doe");
paginatedSearchHeaders.put(KeycloakConstants.FIRST_RESULT, 0);
paginatedSearchHeaders.put(KeycloakConstants.MAX_RESULTS, 10);
template.sendBodyAndHeaders("keycloak:admin?operation=searchUsers", null, paginatedSearchHeaders);
// Get user roles
Map<String, Object> rolesHeaders = new HashMap<>();
rolesHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
rolesHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
List<RoleRepresentation> roles = template.requestBodyAndHeaders(
"keycloak:admin?operation=getUserRoles", null, rolesHeaders, List.class); # Search users route
- route:
from:
uri: direct:search-users
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakSearchQuery
simple: "${body[query]}"
- to:
uri: keycloak:admin?operation=searchUsers
- log: "Search results: ${body}"
# Search with pagination
- route:
from:
uri: direct:search-users-paginated
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakSearchQuery
simple: "${body[query]}"
- setHeader:
name: CamelKeycloakFirstResult
simple: "${body[offset]}"
- setHeader:
name: CamelKeycloakMaxResults
simple: "${body[limit]}"
- to:
uri: keycloak:admin?operation=searchUsers
- log: "Found ${body.size} users"
# Get user roles route
- route:
from:
uri: direct:get-user-roles
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=getUserRoles
- log: "User roles: ${body}" Client Role Operations
-
Java
-
YAML
// Create client role
Map<String, Object> clientRoleHeaders = new HashMap<>();
clientRoleHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
clientRoleHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
clientRoleHeaders.put(KeycloakConstants.ROLE_NAME, "service-admin");
clientRoleHeaders.put(KeycloakConstants.ROLE_DESCRIPTION, "Service administrator role");
template.sendBodyAndHeaders("keycloak:admin?operation=createClientRole", null, clientRoleHeaders);
// List client roles
Map<String, Object> listClientRolesHeaders = new HashMap<>();
listClientRolesHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
listClientRolesHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
template.sendBodyAndHeaders("keycloak:admin?operation=listClientRoles", null, listClientRolesHeaders);
// Assign client role to user
Map<String, Object> assignClientRoleHeaders = new HashMap<>();
assignClientRoleHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
assignClientRoleHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
assignClientRoleHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
assignClientRoleHeaders.put(KeycloakConstants.ROLE_NAME, "service-admin");
template.sendBodyAndHeaders("keycloak:admin?operation=assignClientRoleToUser", null, assignClientRoleHeaders);
// Remove client role from user
Map<String, Object> removeClientRoleHeaders = new HashMap<>();
removeClientRoleHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
removeClientRoleHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
removeClientRoleHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
removeClientRoleHeaders.put(KeycloakConstants.ROLE_NAME, "service-admin");
template.sendBodyAndHeaders("keycloak:admin?operation=removeClientRoleFromUser", null, removeClientRoleHeaders); # Create client role route
- route:
from:
uri: direct:create-client-role
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientUuid
simple: "${body[clientUuid]}"
- setHeader:
name: CamelKeycloakRoleName
simple: "${body[roleName]}"
- setHeader:
name: CamelKeycloakRoleDescription
simple: "${body[description]}"
- to:
uri: keycloak:admin?operation=createClientRole
- log: "Created client role: ${header.CamelKeycloakRoleName}"
# List client roles route
- route:
from:
uri: direct:list-client-roles
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientUuid
simple: "${body[clientUuid]}"
- to:
uri: keycloak:admin?operation=listClientRoles
- log: "Client roles: ${body}"
# Assign client role to user
- route:
from:
uri: direct:assign-client-role
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakClientUuid
simple: "${body[clientUuid]}"
- setHeader:
name: CamelKeycloakRoleName
simple: "${body[roleName]}"
- to:
uri: keycloak:admin?operation=assignClientRoleToUser
- log: "Assigned client role to user" Session Management Operations
-
Java
-
YAML
// List user sessions
Map<String, Object> sessionsHeaders = new HashMap<>();
sessionsHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
sessionsHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
List<UserSessionRepresentation> sessions = template.requestBodyAndHeaders(
"keycloak:admin?operation=listUserSessions", null, sessionsHeaders, List.class);
// Logout user (invalidate all sessions)
Map<String, Object> logoutHeaders = new HashMap<>();
logoutHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
logoutHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
template.sendBodyAndHeaders("keycloak:admin?operation=logoutUser", null, logoutHeaders); # List user sessions route
- route:
from:
uri: direct:list-user-sessions
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=listUserSessions
- log: "User sessions: ${body}"
# Logout user route
- route:
from:
uri: direct:logout-user
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=logoutUser
- log: "User logged out: ${header.CamelKeycloakUserId}" Client Scope Operations
-
Java
-
YAML
// Create client scope
Map<String, Object> scopeHeaders = new HashMap<>();
scopeHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
scopeHeaders.put(KeycloakConstants.CLIENT_SCOPE_NAME, "custom-scope");
template.sendBodyAndHeaders("keycloak:admin?operation=createClientScope", null, scopeHeaders);
// List client scopes
template.sendBodyAndHeader("keycloak:admin?operation=listClientScopes", null,
KeycloakConstants.REALM_NAME, "my-realm");
// Get client scope
Map<String, Object> getScopeHeaders = new HashMap<>();
getScopeHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
getScopeHeaders.put(KeycloakConstants.CLIENT_SCOPE_ID, "scope-id-123");
template.sendBodyAndHeaders("keycloak:admin?operation=getClientScope", null, getScopeHeaders);
// Delete client scope
Map<String, Object> deleteScopeHeaders = new HashMap<>();
deleteScopeHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
deleteScopeHeaders.put(KeycloakConstants.CLIENT_SCOPE_ID, "scope-id-123");
template.sendBodyAndHeaders("keycloak:admin?operation=deleteClientScope", null, deleteScopeHeaders); # Create client scope route
- route:
from:
uri: direct:create-client-scope
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientScopeName
simple: "${body[scopeName]}"
- to:
uri: keycloak:admin?operation=createClientScope
- log: "Created client scope: ${header.CamelKeycloakClientScopeName}"
# List client scopes route
- route:
from:
uri: direct:list-client-scopes
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- to:
uri: keycloak:admin?operation=listClientScopes
- log: "Client scopes: ${body}"
# Get client scope route
- route:
from:
uri: direct:get-client-scope
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientScopeId
simple: "${body[scopeId]}"
- to:
uri: keycloak:admin?operation=getClientScope
- log: "Client scope: ${body}" Identity Provider Operations
Identity providers allow you to configure external authentication systems like LDAP, Active Directory, Google, GitHub, and other SAML/OIDC providers.
// Create OIDC identity provider
Map<String, Object> idpHeaders = new HashMap<>();
idpHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
IdentityProviderRepresentation idp = new IdentityProviderRepresentation();
idp.setAlias("google-idp");
idp.setProviderId("google");
idp.setEnabled(true);
idp.setDisplayName("Google Login");
// Configure provider-specific settings
Map<String, String> config = new HashMap<>();
config.put("clientId", "google-client-id");
config.put("clientSecret", "google-client-secret");
idp.setConfig(config);
template.sendBodyAndHeaders("keycloak:admin?operation=createIdentityProvider&pojoRequest=true",
idp, idpHeaders);
// List all identity providers
template.sendBodyAndHeader("keycloak:admin?operation=listIdentityProviders", null,
KeycloakConstants.REALM_NAME, "my-realm");
// Get specific identity provider
Map<String, Object> getIdpHeaders = new HashMap<>();
getIdpHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
getIdpHeaders.put(KeycloakConstants.IDP_ALIAS, "google-idp");
IdentityProviderRepresentation provider = template.requestBodyAndHeaders(
"keycloak:admin?operation=getIdentityProvider", null, getIdpHeaders,
IdentityProviderRepresentation.class);
// Delete identity provider
template.sendBodyAndHeaders("keycloak:admin?operation=deleteIdentityProvider", null, getIdpHeaders); User Attribute Operations
User attributes allow you to store custom key-value pairs on user accounts for additional metadata or application-specific data.
-
Java
-
YAML
// Set user attribute
Map<String, Object> attrHeaders = new HashMap<>();
attrHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
attrHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
attrHeaders.put(KeycloakConstants.ATTRIBUTE_NAME, "department");
attrHeaders.put(KeycloakConstants.ATTRIBUTE_VALUE, "Engineering");
template.sendBodyAndHeaders("keycloak:admin?operation=setUserAttribute", null, attrHeaders);
// Get all user attributes
Map<String, Object> getUserAttrHeaders = new HashMap<>();
getUserAttrHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
getUserAttrHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
Map<String, List<String>> attributes = template.requestBodyAndHeaders(
"keycloak:admin?operation=getUserAttributes", null, getUserAttrHeaders, Map.class);
// Delete user attribute
Map<String, Object> delAttrHeaders = new HashMap<>();
delAttrHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
delAttrHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
delAttrHeaders.put(KeycloakConstants.ATTRIBUTE_NAME, "department");
template.sendBodyAndHeaders("keycloak:admin?operation=deleteUserAttribute", null, delAttrHeaders); # Set user attribute
- route:
from:
uri: direct:set-user-attribute
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakAttributeName
simple: "${body[attributeName]}"
- setHeader:
name: CamelKeycloakAttributeValue
simple: "${body[attributeValue]}"
- to:
uri: keycloak:admin?operation=setUserAttribute
- log: "Set attribute ${header.CamelKeycloakAttributeName}"
# Get user attributes
- route:
from:
uri: direct:get-user-attributes
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=getUserAttributes
- log: "User attributes: ${body}"
# Delete user attribute
- route:
from:
uri: direct:delete-user-attribute
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakAttributeName
simple: "${body[attributeName]}"
- to:
uri: keycloak:admin?operation=deleteUserAttribute
- log: "Deleted attribute ${header.CamelKeycloakAttributeName}" User Credential and Action Operations
Manage user credentials and trigger user actions such as email verification and password resets.
-
Java
-
YAML
// Get user credentials
Map<String, Object> credHeaders = new HashMap<>();
credHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
credHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
List<CredentialRepresentation> credentials = template.requestBodyAndHeaders(
"keycloak:admin?operation=getUserCredentials", null, credHeaders, List.class);
// Delete specific credential
Map<String, Object> delCredHeaders = new HashMap<>();
delCredHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
delCredHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
delCredHeaders.put(KeycloakConstants.CREDENTIAL_ID, "credential-id-456");
template.sendBodyAndHeaders("keycloak:admin?operation=deleteUserCredential", null, delCredHeaders);
// Send verification email
Map<String, Object> verifyHeaders = new HashMap<>();
verifyHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
verifyHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
template.sendBodyAndHeaders("keycloak:admin?operation=sendVerifyEmail", null, verifyHeaders);
// Send password reset email
Map<String, Object> resetHeaders = new HashMap<>();
resetHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
resetHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
template.sendBodyAndHeaders("keycloak:admin?operation=sendPasswordResetEmail", null, resetHeaders);
// Add required action
Map<String, Object> actionHeaders = new HashMap<>();
actionHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
actionHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
actionHeaders.put(KeycloakConstants.REQUIRED_ACTION, "VERIFY_EMAIL");
template.sendBodyAndHeaders("keycloak:admin?operation=addRequiredAction", null, actionHeaders);
// Execute multiple actions via email
Map<String, Object> execHeaders = new HashMap<>();
execHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
execHeaders.put(KeycloakConstants.USER_ID, "user-id-123");
execHeaders.put(KeycloakConstants.ACTIONS, Arrays.asList("UPDATE_PASSWORD", "VERIFY_EMAIL"));
execHeaders.put(KeycloakConstants.REDIRECT_URI, "https://myapp.com/auth/callback");
execHeaders.put(KeycloakConstants.LIFESPAN, 3600); // 1 hour
template.sendBodyAndHeaders("keycloak:admin?operation=executeActionsEmail", null, execHeaders); # Get user credentials
- route:
from:
uri: direct:get-user-credentials
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=getUserCredentials
- log: "User credentials: ${body}"
# Send verification email
- route:
from:
uri: direct:send-verify-email
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=sendVerifyEmail
- log: "Verification email sent to user ${header.CamelKeycloakUserId}"
# Send password reset email
- route:
from:
uri: direct:send-password-reset
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- to:
uri: keycloak:admin?operation=sendPasswordResetEmail
- log: "Password reset email sent"
# Add required action
- route:
from:
uri: direct:add-required-action
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakRequiredAction
constant: "VERIFY_EMAIL"
- to:
uri: keycloak:admin?operation=addRequiredAction
- log: "Added required action VERIFY_EMAIL"
# Execute actions email
- route:
from:
uri: direct:execute-actions-email
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakActions
constant:
- "UPDATE_PASSWORD"
- "VERIFY_EMAIL"
- setHeader:
name: CamelKeycloakRedirectUri
constant: "https://myapp.com/auth/callback"
- setHeader:
name: CamelKeycloakLifespan
constant: 3600
- to:
uri: keycloak:admin?operation=executeActionsEmail
- log: "Sent actions email to user" Client Secret Management
Retrieve and rotate client secrets for confidential clients.
-
Java
-
YAML
// Get client secret
Map<String, Object> getSecretHeaders = new HashMap<>();
getSecretHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
getSecretHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
CredentialRepresentation secret = template.requestBodyAndHeaders(
"keycloak:admin?operation=getClientSecret", null, getSecretHeaders,
CredentialRepresentation.class);
System.out.println("Client secret: " + secret.getValue());
// Regenerate client secret (rotates the secret)
Map<String, Object> regenHeaders = new HashMap<>();
regenHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
regenHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
CredentialRepresentation newSecret = template.requestBodyAndHeaders(
"keycloak:admin?operation=regenerateClientSecret", null, regenHeaders,
CredentialRepresentation.class);
System.out.println("New client secret: " + newSecret.getValue()); # Get client secret
- route:
from:
uri: direct:get-client-secret
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientUuid
simple: "${body[clientUuid]}"
- to:
uri: keycloak:admin?operation=getClientSecret
- log: "Retrieved client secret"
# Regenerate client secret
- route:
from:
uri: direct:regenerate-secret
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakClientUuid
simple: "${body[clientUuid]}"
- to:
uri: keycloak:admin?operation=regenerateClientSecret
- log: "Regenerated client secret: ${body.value}"
- to: "direct:notify-secret-rotation" Authorization Services Operations
Keycloak Authorization Services provide fine-grained authorization with resources, policies, and permissions.
| These operations require a client with authorization services enabled in Keycloak. |
// Create authorization resource
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("documents");
resource.setType("urn:myapp:resources:document");
resource.setUris(Collections.singleton("/documents/*"));
resource.addScope("read", "write", "delete");
Map<String, Object> resourceHeaders = new HashMap<>();
resourceHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
resourceHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
Response response = template.requestBodyAndHeaders(
"keycloak:admin?operation=createResource&pojoRequest=true",
resource, resourceHeaders, Response.class);
// List all resources
template.sendBodyAndHeaders("keycloak:admin?operation=listResources", null, resourceHeaders);
// Create role-based policy
PolicyRepresentation policy = new PolicyRepresentation();
policy.setName("admin-policy");
policy.setType("role");
policy.setDescription("Only admins can access");
Map<String, Object> policyConfig = new HashMap<>();
policyConfig.put("roles", "[{\"id\":\"admin-role-id\",\"required\":true}]");
policy.setConfig(policyConfig);
Map<String, Object> policyHeaders = new HashMap<>();
policyHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
policyHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
template.sendBodyAndHeaders("keycloak:admin?operation=createResourcePolicy&pojoRequest=true",
policy, policyHeaders);
// Create resource permission
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName("document-permission");
permission.addResource("documents");
permission.addPolicy("admin-policy");
Map<String, Object> permHeaders = new HashMap<>();
permHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
permHeaders.put(KeycloakConstants.CLIENT_UUID, "client-uuid-123");
template.sendBodyAndHeaders("keycloak:admin?operation=createResourcePermission&pojoRequest=true",
permission, permHeaders);
// List all permissions
template.sendBodyAndHeaders("keycloak:admin?operation=listResourcePermissions", null, permHeaders); Bulk Operations
Bulk operations allow you to perform multiple operations in a single request, improving efficiency and reducing network overhead. These operations are particularly useful for provisioning, migrations, and large-scale administrative tasks.
Bulk Create Users
Create multiple users in a single operation with detailed results for each user.
-
Java
-
YAML
// Create multiple users at once
List<UserRepresentation> users = new ArrayList<>();
for (int i = 1; i <= 100; i++) {
UserRepresentation user = new UserRepresentation();
user.setUsername("user" + i);
user.setEmail("user" + i + "@company.com");
user.setFirstName("User");
user.setLastName("" + i);
user.setEnabled(true);
users.add(user);
}
Map<String, Object> headers = new HashMap<>();
headers.put(KeycloakConstants.REALM_NAME, "my-realm");
headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true); // Continue even if some users fail
Map<String, Object> result = template.requestBodyAndHeaders(
"keycloak:admin?operation=bulkCreateUsers", users, headers, Map.class);
// Result contains summary and details
System.out.println("Total: " + result.get("total"));
System.out.println("Success: " + result.get("success"));
System.out.println("Failed: " + result.get("failed"));
// Detailed results for each user
List<Map<String, Object>> results = (List<Map<String, Object>>) result.get("results");
for (Map<String, Object> userResult : results) {
System.out.println("User: " + userResult.get("username") + " - " + userResult.get("status"));
} # Bulk create users route
- route:
from:
uri: direct:bulk-create-users
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkCreateUsers
- log: "Created ${body[success]} out of ${body[total]} users" Bulk Delete Users
Delete multiple users by user IDs or usernames in a single operation.
-
Java
-
YAML
// Delete by user IDs
List<String> userIds = List.of("user-id-1", "user-id-2", "user-id-3");
Map<String, Object> headers = new HashMap<>();
headers.put(KeycloakConstants.REALM_NAME, "my-realm");
headers.put(KeycloakConstants.USER_IDS, userIds);
headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
Map<String, Object> result = template.requestBodyAndHeaders(
"keycloak:admin?operation=bulkDeleteUsers", null, headers, Map.class);
// Or delete by usernames
List<String> usernames = List.of("user1", "user2", "user3");
headers.put(KeycloakConstants.USERNAMES, usernames);
headers.remove(KeycloakConstants.USER_IDS);
result = template.requestBodyAndHeaders(
"keycloak:admin?operation=bulkDeleteUsers", null, headers, Map.class);
System.out.println("Deleted " + result.get("success") + " users"); # Bulk delete users by usernames
- route:
from:
uri: direct:bulk-delete-users
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUsernames
simple: "${body}" # List of usernames
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkDeleteUsers
- log: "Deleted ${body[success]} out of ${body[total]} users" Bulk Assign Roles to User
Assign multiple roles to a single user in one operation.
-
Java
-
YAML
// Assign multiple roles to one user
List<String> roleNames = List.of("admin", "manager", "developer");
Map<String, Object> headers = new HashMap<>();
headers.put(KeycloakConstants.REALM_NAME, "my-realm");
headers.put(KeycloakConstants.USER_ID, "user-id-123");
headers.put(KeycloakConstants.ROLE_NAMES, roleNames);
headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
Map<String, Object> result = template.requestBodyAndHeaders(
"keycloak:admin?operation=bulkAssignRolesToUser", null, headers, Map.class);
System.out.println("Assigned " + result.get("assigned") + " roles to user");
// Detailed results
List<Map<String, Object>> results = (List<Map<String, Object>>) result.get("results");
for (Map<String, Object> roleResult : results) {
System.out.println("Role: " + roleResult.get("roleName") + " - " + roleResult.get("status"));
} # Assign multiple roles to a user
- route:
from:
uri: direct:assign-roles-to-user
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setHeader:
name: CamelKeycloakRoleNames
simple: "${body[roles]}" # List of role names
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkAssignRolesToUser
- log: "Assigned ${body[assigned]} roles to user" Bulk Assign Role to Users
Assign a single role to multiple users in one operation.
-
Java
-
YAML
// Assign one role to multiple users
List<String> userIds = List.of("user-id-1", "user-id-2", "user-id-3");
Map<String, Object> headers = new HashMap<>();
headers.put(KeycloakConstants.REALM_NAME, "my-realm");
headers.put(KeycloakConstants.ROLE_NAME, "developer");
headers.put(KeycloakConstants.USER_IDS, userIds);
headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
Map<String, Object> result = template.requestBodyAndHeaders(
"keycloak:admin?operation=bulkAssignRoleToUsers", null, headers, Map.class);
System.out.println("Assigned role to " + result.get("success") + " users");
// Or use usernames instead of IDs
List<String> usernames = List.of("user1", "user2", "user3");
headers.put(KeycloakConstants.USERNAMES, usernames);
headers.remove(KeycloakConstants.USER_IDS);
result = template.requestBodyAndHeaders(
"keycloak:admin?operation=bulkAssignRoleToUsers", null, headers, Map.class); # Assign a role to multiple users
- route:
from:
uri: direct:assign-role-to-users
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakRoleName
constant: "developer"
- setHeader:
name: CamelKeycloakUsernames
simple: "${body}" # List of usernames
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkAssignRoleToUsers
- log: "Assigned role to ${body[success]} out of ${body[total]} users" Bulk Update Users
Update multiple users in a single operation.
-
Java
-
YAML
// Update multiple users
List<UserRepresentation> users = new ArrayList<>();
// Fetch users and update them
List<UserRepresentation> existingUsers = template.requestBodyAndHeader(
"keycloak:admin?operation=listUsers", null,
KeycloakConstants.REALM_NAME, "my-realm", List.class);
for (UserRepresentation user : existingUsers) {
// Update user properties
user.setFirstName("Updated");
user.setEnabled(true);
users.add(user);
}
Map<String, Object> headers = new HashMap<>();
headers.put(KeycloakConstants.REALM_NAME, "my-realm");
headers.put(KeycloakConstants.USERS, users);
headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
Map<String, Object> result = template.requestBodyAndHeaders(
"keycloak:admin?operation=bulkUpdateUsers", null, headers, Map.class);
System.out.println("Updated " + result.get("success") + " users");
// Detailed results
List<Map<String, Object>> results = (List<Map<String, Object>>) result.get("results");
for (Map<String, Object> userResult : results) {
System.out.println("User: " + userResult.get("username") + " - " + userResult.get("status"));
} # Bulk update users
- route:
from:
uri: direct:bulk-update-users
steps:
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkUpdateUsers
- log: "Updated ${body[success]} out of ${body[total]} users" Bulk Operations Response Format
All bulk operations return a consistent response format with the following structure:
{
"total": 10, // Total number of items processed
"success": 8, // Number of successful operations
"failed": 2, // Number of failed operations
"results": [ // Detailed results for each item
{
"username": "user1",
"status": "success",
"statusCode": 201
},
{
"username": "user2",
"status": "failed",
"error": "User already exists"
}
]
} Error Handling in Bulk Operations
Bulk operations support the CamelKeycloakContinueOnError header to control error handling behavior:
-
Java
-
YAML
// Continue processing even if some operations fail
headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
// Stop on first error (default behavior)
headers.put(KeycloakConstants.CONTINUE_ON_ERROR, false); # Continue on error
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
# Stop on first error
- setHeader:
name: CamelKeycloakContinueOnError
constant: false Complete Bulk Operations Example
-
Java
-
YAML
public class BulkUserProvisioningRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
// Provision users from CSV file
from("file:data/incoming?noop=true")
.routeId("bulk-user-provisioning")
.log("Processing user provisioning file: ${header.CamelFileName}")
// Parse CSV to user objects
.unmarshal().csv()
.process(exchange -> {
List<List<String>> csvData = exchange.getIn().getBody(List.class);
List<UserRepresentation> users = new ArrayList<>();
// Skip header row
for (int i = 1; i < csvData.size(); i++) {
List<String> row = csvData.get(i);
UserRepresentation user = new UserRepresentation();
user.setUsername(row.get(0));
user.setEmail(row.get(1));
user.setFirstName(row.get(2));
user.setLastName(row.get(3));
user.setEnabled(true);
users.add(user);
}
exchange.getIn().setBody(users);
})
// Bulk create users
.setHeader(KeycloakConstants.REALM_NAME, constant("my-realm"))
.setHeader(KeycloakConstants.CONTINUE_ON_ERROR, constant(true))
.to("keycloak:admin?operation=bulkCreateUsers")
// Log results
.process(exchange -> {
Map<String, Object> result = exchange.getIn().getBody(Map.class);
log.info("User provisioning completed: {} succeeded, {} failed out of {}",
result.get("success"), result.get("failed"), result.get("total"));
})
// Assign default role to all successfully created users
.filter(simple("${body[success]} > 0"))
.process(exchange -> {
// Extract user IDs from results
Map<String, Object> createResult = exchange.getIn().getBody(Map.class);
// Get created users and assign role...
});
// Bulk role assignment from JSON
from("rest:post:/users/assign-roles")
.routeId("bulk-assign-roles")
.log("Bulk assigning roles to users")
.unmarshal().json()
.setHeader(KeycloakConstants.REALM_NAME, simple("${body[realm]}"))
.setHeader(KeycloakConstants.USER_ID, simple("${body[userId]}"))
.setBody(simple("${body[roles]}"))
.setHeader(KeycloakConstants.CONTINUE_ON_ERROR, constant(true))
.to("keycloak:admin?operation=bulkAssignRolesToUser")
.marshal().json()
.setHeader("Content-Type", constant("application/json"));
// Cleanup inactive users
from("timer:cleanup?period=86400000") // Daily
.routeId("cleanup-inactive-users")
.log("Starting inactive user cleanup")
.setHeader(KeycloakConstants.REALM_NAME, constant("my-realm"))
.to("keycloak:admin?operation=listUsers")
.process(exchange -> {
List<UserRepresentation> allUsers = exchange.getIn().getBody(List.class);
List<String> inactiveUserIds = new ArrayList<>();
// Identify inactive users (custom logic)
for (UserRepresentation user : allUsers) {
// Check last login, attributes, etc.
if (isInactive(user)) {
inactiveUserIds.add(user.getId());
}
}
exchange.getIn().setBody(inactiveUserIds);
})
.filter(simple("${body.size} > 0"))
.setHeader(KeycloakConstants.CONTINUE_ON_ERROR, constant(true))
.to("keycloak:admin?operation=bulkDeleteUsers")
.log("Deleted ${body[success]} inactive users");
}
private boolean isInactive(UserRepresentation user) {
// Custom logic to determine if user is inactive
return false;
}
} # Bulk user provisioning from CSV
- route:
id: bulk-user-provisioning
from:
uri: file:data/incoming?noop=true
steps:
- log: "Processing user provisioning file: ${header.CamelFileName}"
- unmarshal:
csv: {}
- process:
ref: csvToUsersProcessor
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkCreateUsers
- log: "Provisioning completed: ${body[success]} succeeded, ${body[failed]} failed"
# Bulk role assignment API
- rest:
post:
- uri: /users/assign-roles
to: direct:bulk-assign-roles
- route:
id: bulk-assign-roles
from:
uri: direct:bulk-assign-roles
steps:
- unmarshal:
json: {}
- setHeader:
name: CamelKeycloakRealmName
simple: "${body[realm]}"
- setHeader:
name: CamelKeycloakUserId
simple: "${body[userId]}"
- setBody:
simple: "${body[roles]}"
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkAssignRolesToUser
- marshal:
json: {}
# Daily cleanup of inactive users
- route:
id: cleanup-inactive-users
from:
uri: timer:cleanup?period=86400000
steps:
- log: "Starting inactive user cleanup"
- setHeader:
name: CamelKeycloakRealmName
constant: "my-realm"
- to:
uri: keycloak:admin?operation=listUsers
- process:
ref: identifyInactiveUsersProcessor
- filter:
simple: "${body.size} > 0"
steps:
- setHeader:
name: CamelKeycloakContinueOnError
constant: true
- to:
uri: keycloak:admin?operation=bulkDeleteUsers
- log: "Deleted ${body[success]} inactive users" Best Practices for Bulk Operations
-
Use Continue on Error: Always set
continueOnError=truefor bulk operations to get complete feedback on all items -
Monitor Results: Check the results map to identify and handle failures appropriately
-
Batch Size: For very large datasets, consider splitting into smaller batches (e.g., 100-500 users per batch)
-
Error Handling: Implement proper error handling and retry logic for failed items
-
Logging: Log detailed results for audit and troubleshooting purposes
-
Testing: Test bulk operations with small datasets first before running in production
-
Performance: Bulk operations are more efficient than individual operations but still require appropriate timeout settings
-
Transactions: Note that Keycloak operations are not transactional - some items may succeed while others fail
Complete Producer Example
-
Java
-
YAML
public class KeycloakManagementRoutes extends RouteBuilder {
@Override
public void configure() throws Exception {
// Configure Keycloak component
KeycloakComponent keycloak = getContext().getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master");
config.setUsername("admin");
config.setPassword("admin");
keycloak.setConfiguration(config);
// Comprehensive user management route
from("direct:setup-user-environment")
.routeId("setup-user-environment")
.log("Setting up user environment...")
// Step 1: Create realm
.setHeader(KeycloakConstants.REALM_NAME, constant("my-company"))
.to("keycloak:admin?operation=createRealm")
.log("Created realm: my-company")
// Step 2: Create roles
.setHeader(KeycloakConstants.ROLE_NAME, constant("admin"))
.setHeader(KeycloakConstants.ROLE_DESCRIPTION, constant("Administrator role"))
.to("keycloak:admin?operation=createRole")
.log("Created admin role")
.setHeader(KeycloakConstants.ROLE_NAME, constant("user"))
.setHeader(KeycloakConstants.ROLE_DESCRIPTION, constant("Standard user role"))
.to("keycloak:admin?operation=createRole")
.log("Created user role")
// Step 3: Create client
.setHeader("CamelKeycloakClientId", constant("my-app"))
.setHeader("CamelKeycloakClientSecretRequired", constant(true))
.setHeader("CamelKeycloakClientDirectAccessGrantsEnabled", constant(true))
.to("keycloak:admin?operation=createClient")
.log("Created client: my-app")
// Step 4: Create users
.setHeader(KeycloakConstants.USERNAME, constant("admin.user"))
.setHeader(KeycloakConstants.USER_EMAIL, constant("admin@company.com"))
.setHeader(KeycloakConstants.USER_FIRST_NAME, constant("Admin"))
.setHeader(KeycloakConstants.USER_LAST_NAME, constant("User"))
.to("keycloak:admin?operation=createUser")
.log("Created admin user")
// Step 5: Set password
.setHeader("CamelKeycloakUserPassword", constant("admin123"))
.setHeader("CamelKeycloakUserPasswordTemporary", constant(false))
.to("keycloak:admin?operation=setUserPassword")
.log("Set admin user password")
// Step 6: Assign role
.setHeader(KeycloakConstants.ROLE_NAME, constant("admin"))
.to("keycloak:admin?operation=assignRoleToUser")
.log("Assigned admin role to user")
.transform().constant("User environment setup completed successfully");
// User management API routes
from("rest:post:/users")
.routeId("create-user-api")
.log("Creating user: ${body}")
.setHeader(KeycloakConstants.REALM_NAME, constant("my-company"))
.setHeader(KeycloakConstants.USERNAME, jsonpath("$.username"))
.setHeader(KeycloakConstants.USER_EMAIL, jsonpath("$.email"))
.setHeader(KeycloakConstants.USER_FIRST_NAME, jsonpath("$.firstName"))
.setHeader(KeycloakConstants.USER_LAST_NAME, jsonpath("$.lastName"))
.to("keycloak:admin?operation=createUser")
.setHeader("Content-Type", constant("application/json"))
.transform().constant("{\"status\": \"success\", \"message\": \"User created\"}");
from("rest:get:/users")
.routeId("list-users-api")
.log("Listing users")
.setHeader(KeycloakConstants.REALM_NAME, constant("my-company"))
.to("keycloak:admin?operation=listUsers")
.setHeader("Content-Type", constant("application/json"));
from("rest:delete:/users/{username}")
.routeId("delete-user-api")
.log("Deleting user: ${header.username}")
.setHeader(KeycloakConstants.REALM_NAME, constant("my-company"))
.setHeader(KeycloakConstants.USERNAME, header("username"))
.to("keycloak:admin?operation=deleteUser")
.setHeader("Content-Type", constant("application/json"))
.transform().constant("{\"status\": \"success\", \"message\": \"User deleted\"}");
}
} # Complete Keycloak producer configuration
- route:
id: setup-user-environment
from:
uri: direct:setup-user-environment
steps:
- log: "Setting up user environment..."
# Step 1: Create realm
- setHeader:
name: CamelKeycloakRealmName
constant: "my-company"
- to:
uri: keycloak:admin?operation=createRealm
- log: "Created realm: my-company"
# Step 2: Create admin role
- setHeader:
name: CamelKeycloakRoleName
constant: "admin"
- setHeader:
name: CamelKeycloakRoleDescription
constant: "Administrator role"
- to:
uri: keycloak:admin?operation=createRole
- log: "Created admin role"
# Step 3: Create user role
- setHeader:
name: CamelKeycloakRoleName
constant: "user"
- setHeader:
name: CamelKeycloakRoleDescription
constant: "Standard user role"
- to:
uri: keycloak:admin?operation=createRole
- log: "Created user role"
# Step 4: Create client
- setHeader:
name: CamelKeycloakClientId
constant: "my-app"
- setHeader:
name: CamelKeycloakClientSecretRequired
constant: true
- setHeader:
name: CamelKeycloakClientDirectAccessGrantsEnabled
constant: true
- to:
uri: keycloak:admin?operation=createClient
- log: "Created client: my-app"
# Step 5: Create admin user
- setHeader:
name: CamelKeycloakUsername
constant: "admin.user"
- setHeader:
name: CamelKeycloakUserEmail
constant: "admin@company.com"
- setHeader:
name: CamelKeycloakUserFirstName
constant: "Admin"
- setHeader:
name: CamelKeycloakUserLastName
constant: "User"
- to:
uri: keycloak:admin?operation=createUser
- log: "Created admin user"
# Step 6: Set password
- setHeader:
name: CamelKeycloakUserPassword
constant: "admin123"
- setHeader:
name: CamelKeycloakUserPasswordTemporary
constant: false
- to:
uri: keycloak:admin?operation=setUserPassword
- log: "Set admin user password"
# Step 7: Assign role
- setHeader:
name: CamelKeycloakRoleName
constant: "admin"
- to:
uri: keycloak:admin?operation=assignRoleToUser
- log: "Assigned admin role to user"
- transform:
constant: "User environment setup completed successfully"
# REST API routes for user management
- rest:
path: /users
post:
- to: direct:create-user-api
- rest:
path: /users
get:
- to: direct:list-users-api
- rest:
path: /users/{username}
delete:
- to: direct:delete-user-api
# Route implementations
- route:
id: create-user-api
from:
uri: direct:create-user-api
steps:
- log: "Creating user: ${body}"
- setHeader:
name: CamelKeycloakRealmName
constant: "my-company"
- setHeader:
name: CamelKeycloakUsername
jsonpath: "$.username"
- setHeader:
name: CamelKeycloakUserEmail
jsonpath: "$.email"
- setHeader:
name: CamelKeycloakUserFirstName
jsonpath: "$.firstName"
- setHeader:
name: CamelKeycloakUserLastName
jsonpath: "$.lastName"
- to:
uri: keycloak:admin?operation=createUser
- setHeader:
name: Content-Type
constant: "application/json"
- transform:
constant: '{"status": "success", "message": "User created"}'
- route:
id: list-users-api
from:
uri: direct:list-users-api
steps:
- log: "Listing users"
- setHeader:
name: CamelKeycloakRealmName
constant: "my-company"
- to:
uri: keycloak:admin?operation=listUsers
- setHeader:
name: Content-Type
constant: "application/json"
- route:
id: delete-user-api
from:
uri: direct:delete-user-api
steps:
- log: "Deleting user: ${header.username}"
- setHeader:
name: CamelKeycloakRealmName
constant: "my-company"
- setHeader:
name: CamelKeycloakUsername
header: "username"
- to:
uri: keycloak:admin?operation=deleteUser
- setHeader:
name: Content-Type
constant: "application/json"
- transform:
constant: '{"status": "success", "message": "User deleted"}'
# Component configuration
camel:
component:
keycloak:
server-url: "http://localhost:8080"
realm: "master"
username: "admin"
password: "admin" Consumer Operations
The Keycloak consumer allows you to poll and consume events from a Keycloak instance. This is useful for monitoring user activities, admin actions, and implementing event-driven workflows based on Keycloak events.
Overview
The consumer supports two types of events:
-
User Events (
events) - Login attempts, logout events, register events, etc. -
Admin Events (
admin-events) - User created, role assigned, realm updated, etc.
The consumer uses a polling mechanism with fingerprint-based deduplication to ensure events are not processed multiple times.
Configuration
The consumer supports the same authentication methods as the producer (access token, refresh token, username/password, or client credentials):
-
Java (Access Token)
-
Java (Username/Password)
-
YAML
// Configure Keycloak component for consuming with access token
KeycloakComponent keycloak = context.getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master"); // Auth realm
config.setAccessToken("eyJhbGciOiJSUzI1NiIsInR5cC...");
keycloak.setConfiguration(config); // Configure Keycloak component for consuming with username/password
KeycloakComponent keycloak = context.getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master"); // Auth realm
config.setUsername("admin");
config.setPassword("admin");
keycloak.setConfiguration(config); # Configuration in application.yaml (using access token)
camel:
component:
keycloak:
server-url: "http://localhost:8080"
realm: "master"
access-token: "eyJhbGciOiJSUzI1NiIsInR5cC..."
# Or using username/password
camel:
component:
keycloak:
server-url: "http://localhost:8080"
realm: "master"
username: "admin"
password: "admin" Consuming Admin Events
Admin events are generated when administrative operations are performed in Keycloak, such as creating users, assigning roles, or updating realm settings.
-
Java
-
YAML
// Consume admin events from a specific realm
from("keycloak:adminEvents"
+ "?realm=my-realm"
+ "&eventType=admin-events"
+ "&maxResults=50"
+ "&initialDelay=1000"
+ "&delay=5000")
.log("Received admin event: ${body}")
.choice()
.when(simple("${body.operationType} == 'CREATE'"))
.log("Resource created: ${body.resourceType} at ${body.resourcePath}")
.when(simple("${body.operationType} == 'UPDATE'"))
.log("Resource updated: ${body.resourceType}")
.when(simple("${body.operationType} == 'DELETE'"))
.log("Resource deleted: ${body.resourceType}")
.otherwise()
.log("Other operation: ${body.operationType}")
.end()
.to("direct:process-admin-event");
// Process specific admin events
from("direct:process-admin-event")
.process(exchange -> {
AdminEventRepresentation event = exchange.getIn().getBody(AdminEventRepresentation.class);
// Access event details
String operationType = event.getOperationType();
String resourceType = event.getResourceType();
String resourcePath = event.getResourcePath();
long timestamp = event.getTime();
// Get auth details
if (event.getAuthDetails() != null) {
String userId = event.getAuthDetails().getUserId();
String realmId = event.getAuthDetails().getRealmId();
log.info("Operation performed by user: {} in realm: {}", userId, realmId);
}
// Process the event
log.info("Processing {} operation on {} at {}", operationType, resourceType, timestamp);
}); # Consume admin events
- route:
id: consume-admin-events
from:
uri: >
keycloak:adminEvents?
realm=my-realm&
eventType=admin-events&
maxResults=50&
initialDelay=1000&
delay=5000
steps:
- log: "Received admin event: ${body}"
- choice:
when:
- simple: "${body.operationType} == 'CREATE'"
steps:
- log: "Resource created: ${body.resourceType} at ${body.resourcePath}"
- simple: "${body.operationType} == 'UPDATE'"
steps:
- log: "Resource updated: ${body.resourceType}"
- simple: "${body.operationType} == 'DELETE'"
steps:
- log: "Resource deleted: ${body.resourceType}"
otherwise:
steps:
- log: "Other operation: ${body.operationType}"
- to: "direct:process-admin-event"
# Process admin events
- route:
id: process-admin-events
from:
uri: direct:process-admin-event
steps:
- log: "Processing admin event: ${body.operationType} on ${body.resourceType}"
- to: "bean:auditService?method=recordAdminEvent" Consuming User Events
User events track user activities such as logins, logouts, registration, password changes, and more.
-
Java
-
YAML
// Consume user events from a specific realm
from("keycloak:userEvents"
+ "?realm=my-realm"
+ "&eventType=events"
+ "&maxResults=50"
+ "&initialDelay=1000"
+ "&delay=5000")
.log("Received user event: ${body}")
.choice()
.when(simple("${body.type} == 'LOGIN'"))
.log("User logged in: ${body.userId} from IP ${body.ipAddress}")
.to("direct:handle-login")
.when(simple("${body.type} == 'LOGIN_ERROR'"))
.log("Failed login attempt: ${body.userId}")
.to("direct:handle-failed-login")
.when(simple("${body.type} == 'LOGOUT'"))
.log("User logged out: ${body.userId}")
.to("direct:handle-logout")
.when(simple("${body.type} == 'REGISTER'"))
.log("New user registered: ${body.userId}")
.to("direct:handle-registration")
.otherwise()
.log("Other event: ${body.type}")
.end();
// Handle login events
from("direct:handle-login")
.process(exchange -> {
EventRepresentation event = exchange.getIn().getBody(EventRepresentation.class);
String userId = event.getUserId();
String ipAddress = event.getIpAddress();
long timestamp = event.getTime();
// Access event details
if (event.getDetails() != null) {
String username = event.getDetails().get("username");
log.info("User {} logged in from {} at {}", username, ipAddress, timestamp);
}
})
.to("bean:analyticsService?method=recordLogin");
// Handle failed login attempts
from("direct:handle-failed-login")
.process(exchange -> {
EventRepresentation event = exchange.getIn().getBody(EventRepresentation.class);
String ipAddress = event.getIpAddress();
// Check for suspicious activity
log.warn("Failed login attempt from IP: {}", ipAddress);
})
.to("bean:securityService?method=checkFailedAttempts"); # Consume user events
- route:
id: consume-user-events
from:
uri: >
keycloak:userEvents?
realm=my-realm&
eventType=events&
maxResults=50&
initialDelay=1000&
delay=5000
steps:
- log: "Received user event: ${body}"
- choice:
when:
- simple: "${body.type} == 'LOGIN'"
steps:
- log: "User logged in: ${body.userId} from IP ${body.ipAddress}"
- to: "direct:handle-login"
- simple: "${body.type} == 'LOGIN_ERROR'"
steps:
- log: "Failed login attempt: ${body.userId}"
- to: "direct:handle-failed-login"
- simple: "${body.type} == 'LOGOUT'"
steps:
- log: "User logged out: ${body.userId}"
- to: "direct:handle-logout"
- simple: "${body.type} == 'REGISTER'"
steps:
- log: "New user registered: ${body.userId}"
- to: "direct:handle-registration"
otherwise:
steps:
- log: "Other event: ${body.type}"
# Handle login events
- route:
id: handle-login
from:
uri: direct:handle-login
steps:
- log: "Processing login event for user ${body.userId}"
- to: "bean:analyticsService?method=recordLogin"
# Handle failed login
- route:
id: handle-failed-login
from:
uri: direct:handle-failed-login
steps:
- log: "Processing failed login from ${body.ipAddress}"
- to: "bean:securityService?method=checkFailedAttempts" Filtering Events
You can filter events using various options to narrow down the events you want to consume:
-
Java
-
YAML
// Filter admin events by operation type
from("keycloak:adminEvents"
+ "?realm=my-realm"
+ "&eventType=admin-events"
+ "&operationTypes=CREATE,UPDATE,DELETE"
+ "&maxResults=100")
.log("Filtered admin event: ${body}");
// Filter user events by type
from("keycloak:userEvents"
+ "?realm=my-realm"
+ "&eventType=events"
+ "&types=LOGIN,LOGOUT,REGISTER"
+ "&maxResults=100")
.log("Filtered user event: ${body}");
// Filter by date range
from("keycloak:adminEvents"
+ "?realm=my-realm"
+ "&eventType=admin-events"
+ "&dateFrom=1609459200000" // milliseconds since epoch
+ "&dateTo=1640995200000"
+ "&maxResults=100")
.log("Events in date range: ${body}");
// Filter by user and client
from("keycloak:userEvents"
+ "?realm=my-realm"
+ "&eventType=events"
+ "&user=user-id-123"
+ "&client=my-client-id"
+ "&ipAddress=192.168.1.100"
+ "&maxResults=50")
.log("Specific user events: ${body}"); # Filter admin events by operation type
- route:
from:
uri: >
keycloak:adminEvents?
realm=my-realm&
eventType=admin-events&
operationTypes=CREATE,UPDATE,DELETE&
maxResults=100
steps:
- log: "Filtered admin event: ${body}"
# Filter user events by type
- route:
from:
uri: >
keycloak:userEvents?
realm=my-realm&
eventType=events&
types=LOGIN,LOGOUT,REGISTER&
maxResults=100
steps:
- log: "Filtered user event: ${body}"
# Filter by date range
- route:
from:
uri: >
keycloak:adminEvents?
realm=my-realm&
eventType=admin-events&
dateFrom=1609459200000&
dateTo=1640995200000&
maxResults=100
steps:
- log: "Events in date range: ${body}"
# Filter by user and client
- route:
from:
uri: >
keycloak:userEvents?
realm=my-realm&
eventType=events&
user=user-id-123&
client=my-client-id&
ipAddress=192.168.1.100&
maxResults=50
steps:
- log: "Specific user events: ${body}" Event Processing Patterns
Audit Trail
-
Java
-
YAML
// Create comprehensive audit trail from admin events
from("keycloak:adminEvents"
+ "?realm=my-realm"
+ "&eventType=admin-events"
+ "&maxResults=100"
+ "&delay=10000")
.process(exchange -> {
AdminEventRepresentation event = exchange.getIn().getBody(AdminEventRepresentation.class);
// Build audit record
Map<String, Object> auditRecord = new HashMap<>();
auditRecord.put("timestamp", new Date(event.getTime()));
auditRecord.put("operation", event.getOperationType());
auditRecord.put("resourceType", event.getResourceType());
auditRecord.put("resourcePath", event.getResourcePath());
if (event.getAuthDetails() != null) {
auditRecord.put("userId", event.getAuthDetails().getUserId());
auditRecord.put("ipAddress", event.getAuthDetails().getIpAddress());
}
exchange.getIn().setBody(auditRecord);
})
.marshal().json()
.to("kafka:audit-trail?brokers=localhost:9092")
.to("jdbc:dataSource?useHeadersAsParameters=true"); # Audit trail pattern
- route:
from:
uri: >
keycloak:adminEvents?
realm=my-realm&
eventType=admin-events&
maxResults=100&
delay=10000
steps:
- setBody:
simple: >
{
"timestamp": ${body.time},
"operation": "${body.operationType}",
"resourceType": "${body.resourceType}",
"resourcePath": "${body.resourcePath}",
"userId": "${body.authDetails.userId}",
"ipAddress": "${body.authDetails.ipAddress}"
}
- marshal:
json: {}
- to: "kafka:audit-trail?brokers=localhost:9092"
- to: "jdbc:dataSource?useHeadersAsParameters=true" Security Monitoring
-
Java
-
YAML
// Monitor for security-relevant events
from("keycloak:userEvents"
+ "?realm=my-realm"
+ "&eventType=events"
+ "&types=LOGIN_ERROR,UPDATE_PASSWORD,UPDATE_EMAIL"
+ "&maxResults=50"
+ "&delay=5000")
.filter(simple("${body.type} == 'LOGIN_ERROR'"))
.aggregate(simple("${body.ipAddress}"), new ArrayListAggregationStrategy())
.completionSize(5) // 5 failed attempts
.completionTimeout(300000) // within 5 minutes
.process(exchange -> {
List<EventRepresentation> failedAttempts = exchange.getIn().getBody(List.class);
String ipAddress = failedAttempts.get(0).getIpAddress();
log.warn("SECURITY ALERT: {} failed login attempts from IP: {}",
failedAttempts.size(), ipAddress);
})
.to("direct:block-ip")
.to("direct:send-security-alert"); # Security monitoring pattern
- route:
from:
uri: >
keycloak:userEvents?
realm=my-realm&
eventType=events&
types=LOGIN_ERROR,UPDATE_PASSWORD,UPDATE_EMAIL&
maxResults=50&
delay=5000
steps:
- filter:
simple: "${body.type} == 'LOGIN_ERROR'"
- aggregate:
correlationExpression:
simple: "${body.ipAddress}"
aggregationStrategy: "#arrayListAggregation"
completionSize: 5
completionTimeout: 300000
steps:
- log: "SECURITY ALERT: Multiple failed login attempts from ${body[0].ipAddress}"
- to: "direct:block-ip"
- to: "direct:send-security-alert" User Activity Analytics
-
Java
-
YAML
// Track user activity for analytics
from("keycloak:userEvents"
+ "?realm=my-realm"
+ "&eventType=events"
+ "&types=LOGIN,LOGOUT"
+ "&maxResults=100"
+ "&delay=60000")
.process(exchange -> {
EventRepresentation event = exchange.getIn().getBody(EventRepresentation.class);
// Extract analytics data
Map<String, Object> analytics = new HashMap<>();
analytics.put("userId", event.getUserId());
analytics.put("eventType", event.getType());
analytics.put("timestamp", new Date(event.getTime()));
analytics.put("ipAddress", event.getIpAddress());
analytics.put("sessionId", event.getSessionId());
if (event.getDetails() != null) {
analytics.put("username", event.getDetails().get("username"));
analytics.put("clientId", event.getDetails().get("client_id"));
}
exchange.getIn().setBody(analytics);
})
.to("bean:analyticsService?method=recordActivity")
.to("elasticsearch://keycloak-events?operation=Index&indexName=user-activity"); # User activity analytics
- route:
from:
uri: >
keycloak:userEvents?
realm=my-realm&
eventType=events&
types=LOGIN,LOGOUT&
maxResults=100&
delay=60000
steps:
- setBody:
simple: >
{
"userId": "${body.userId}",
"eventType": "${body.type}",
"timestamp": ${body.time},
"ipAddress": "${body.ipAddress}",
"sessionId": "${body.sessionId}"
}
- to: "bean:analyticsService?method=recordActivity"
- to: "elasticsearch://keycloak-events?operation=Index&indexName=user-activity" Consumer Options
The consumer supports the following configuration options:
| Option | Default | Description |
|---|---|---|
| The Keycloak realm to consume events from (required) | |
| events | Type of events to consume: |
| 100 | Maximum number of events to retrieve per poll |
| 0 | Offset for pagination (first result index) |
| 1000 | Delay before first poll (milliseconds) |
| 500 | Delay between polls (milliseconds) |
Common Filter Options | ||
| Filter events by client ID | |
| Filter events by user ID | |
| Filter events from this timestamp (milliseconds since epoch) | |
| Filter events until this timestamp (milliseconds since epoch) | |
| Filter events by IP address | |
User Event Filters | ||
| Filter by event types (comma-separated, e.g., | |
Admin Event Filters | ||
| Filter by operation types (comma-separated, e.g., | |
| Filter by authentication realm | |
| Filter by authentication client ID | |
| Filter by authentication user ID | |
| Filter by authentication IP address | |
| Filter by resource path | |
Exchange Headers
The consumer sets the following headers on the exchange:
| Header | Description |
|---|---|
| Type of event: |
| Event timestamp (milliseconds since epoch) |
| Realm name where the event occurred |
Message Body
The message body contains:
-
For user events:
org.keycloak.representations.idm.EventRepresentation -
For admin events:
org.keycloak.representations.idm.AdminEventRepresentation
Complete Consumer Example
-
Java
-
YAML
public class KeycloakEventMonitoringRoutes extends RouteBuilder {
@Override
public void configure() throws Exception {
// Configure Keycloak component
KeycloakComponent keycloak = getContext().getComponent("keycloak", KeycloakComponent.class);
KeycloakConfiguration config = new KeycloakConfiguration();
config.setServerUrl("http://localhost:8080");
config.setRealm("master");
config.setUsername("admin");
config.setPassword("admin");
keycloak.setConfiguration(config);
// Consume admin events and send to audit system
from("keycloak:adminEvents"
+ "?realm=production-realm"
+ "&eventType=admin-events"
+ "&operationTypes=CREATE,UPDATE,DELETE"
+ "&maxResults=100"
+ "&delay=10000")
.routeId("admin-events-audit")
.log("Admin event: ${body.operationType} on ${body.resourceType}")
.to("direct:audit-trail");
// Consume user login events for analytics
from("keycloak:userEvents"
+ "?realm=production-realm"
+ "&eventType=events"
+ "&types=LOGIN,LOGOUT"
+ "&maxResults=50"
+ "&delay=30000")
.routeId("user-activity-tracking")
.log("User activity: ${body.type} for user ${body.userId}")
.to("direct:analytics");
// Monitor failed logins for security
from("keycloak:userEvents"
+ "?realm=production-realm"
+ "&eventType=events"
+ "&types=LOGIN_ERROR"
+ "&maxResults=100"
+ "&delay=5000")
.routeId("security-monitoring")
.log("Failed login from IP: ${body.ipAddress}")
.to("direct:security-check");
// Process audit trail
from("direct:audit-trail")
.marshal().json()
.to("kafka:admin-audit?brokers=localhost:9092")
.to("log:audit");
// Process analytics
from("direct:analytics")
.to("bean:analyticsService?method=processUserActivity")
.to("log:analytics");
// Process security alerts
from("direct:security-check")
.to("bean:securityService?method=checkFailedLogin")
.to("log:security");
}
} # Component configuration
camel:
component:
keycloak:
server-url: "http://localhost:8080"
realm: "master"
username: "admin"
password: "admin"
# Routes
- route:
id: admin-events-audit
from:
uri: >
keycloak:adminEvents?
realm=production-realm&
eventType=admin-events&
operationTypes=CREATE,UPDATE,DELETE&
maxResults=100&
delay=10000
steps:
- log: "Admin event: ${body.operationType} on ${body.resourceType}"
- to: "direct:audit-trail"
- route:
id: user-activity-tracking
from:
uri: >
keycloak:userEvents?
realm=production-realm&
eventType=events&
types=LOGIN,LOGOUT&
maxResults=50&
delay=30000
steps:
- log: "User activity: ${body.type} for user ${body.userId}"
- to: "direct:analytics"
- route:
id: security-monitoring
from:
uri: >
keycloak:userEvents?
realm=production-realm&
eventType=events&
types=LOGIN_ERROR&
maxResults=100&
delay=5000
steps:
- log: "Failed login from IP: ${body.ipAddress}"
- to: "direct:security-check"
# Processing routes
- route:
id: process-audit-trail
from:
uri: direct:audit-trail
steps:
- marshal:
json: {}
- to: "kafka:admin-audit?brokers=localhost:9092"
- to: "log:audit"
- route:
id: process-analytics
from:
uri: direct:analytics
steps:
- to: "bean:analyticsService?method=processUserActivity"
- to: "log:analytics"
- route:
id: process-security-check
from:
uri: direct:security-check
steps:
- to: "bean:securityService?method=checkFailedLogin"
- to: "log:security" Enabling Events in Keycloak
Before consuming events, you must enable event logging in Keycloak:
Event Deduplication
The consumer uses fingerprint-based deduplication to prevent processing the same event multiple times:
-
Events are uniquely identified by combining timestamp and event-specific properties
-
Fingerprints are cached per timestamp and cleared when moving to newer timestamps
-
Maximum cache size is 1000 fingerprints to prevent memory issues
-
This ensures reliable event processing even with high event volumes
Security Policies
The Keycloak security policy provides route-level authorization using Keycloak authentication and authorization services.
Features
The Keycloak security policy supports:
-
Role-based authorization - Validate user roles from Keycloak tokens
-
Permission-based authorization - Validate fine-grained permissions using Keycloak Authorization Services
-
Token validation - Verify access tokens from Keycloak
-
Flexible configuration - Support for client credentials and resource owner password flows
Configuration
Basic Setup
-
Java
-
YAML
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
policy.setServerUrl("http://localhost:8080");
policy.setRealm("my-realm");
policy.setClientId("my-client");
policy.setClientSecret("my-client-secret"); - route:
from:
uri: direct:start
steps:
- policy:
ref: keycloakPolicy
- to:
uri: mock:result
# Bean definition in beans configuration
beans:
- name: keycloakPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "my-realm"
clientId: "my-client"
clientSecret: "my-client-secret" Role-based Authorization
-
Java
-
YAML
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
"http://localhost:8080", "my-realm", "my-client", "client-secret");
// Require specific roles (comma-separated)
policy.setRequiredRoles("admin,user");
policy.setAllRolesRequired(true); // User must have ALL roles
from("direct:admin")
.policy(policy)
.to("mock:admin-endpoint"); - route:
from:
uri: direct:admin
steps:
- policy:
ref: adminPolicy
- to:
uri: mock:admin-endpoint
# Bean definition
beans:
- name: adminPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "my-realm"
clientId: "my-client"
clientSecret: "client-secret"
requiredRoles: "admin,user"
allRolesRequired: true Permission-based Authorization
-
Java
-
YAML
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
"http://localhost:8080", "my-realm", "my-client", "client-secret");
// Require specific permissions (comma-separated)
policy.setRequiredPermissions("read:documents,write:documents");
policy.setAllPermissionsRequired(false); // User needs ANY permission
from("direct:documents")
.policy(policy)
.to("mock:documents-endpoint"); - route:
from:
uri: direct:documents
steps:
- policy:
ref: documentsPolicy
- to:
uri: mock:documents-endpoint
# Bean definition
beans:
- name: documentsPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "my-realm"
clientId: "my-client"
clientSecret: "client-secret"
requiredPermissions: "read:documents,write:documents"
allPermissionsRequired: false OAuth 2.0 Token Introspection (RFC 7662)
Token introspection provides real-time validation of access tokens by querying Keycloak’s introspection endpoint. This enables detection of revoked tokens before their expiration time, providing enhanced security compared to local JWT validation.
Overview
The Keycloak security policy supports two token validation methods:
-
Local JWT Parsing (default) - Fast, offline validation by parsing and verifying the JWT signature
-
Token Introspection (RFC 7662) - Real-time validation via Keycloak’s introspection endpoint
Token introspection is particularly useful for:
-
Token Revocation: Detect tokens that have been revoked before their expiration
-
Centralized Validation: Ensure all services validate against the same source of truth
-
Security-Critical Operations: Add an extra layer of validation for sensitive endpoints
-
Compliance Requirements: Meet regulatory requirements for real-time token validation
Configuration
-
Java
-
YAML
// Enable token introspection
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
policy.setServerUrl("http://localhost:8080");
policy.setRealm("my-realm");
policy.setClientId("my-client");
policy.setClientSecret("my-client-secret");
policy.setRequiredRoles("admin");
// Enable introspection with caching
policy.setUseTokenIntrospection(true);
policy.setIntrospectionCacheEnabled(true);
policy.setIntrospectionCacheTtl(120); // 2 minutes
from("direct:secure-endpoint")
.policy(policy)
.to("mock:result"); - route:
from:
uri: direct:secure-endpoint
steps:
- policy:
ref: introspectionPolicy
- to:
uri: mock:result
# Bean definition
beans:
- name: introspectionPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "my-realm"
clientId: "my-client"
clientSecret: "my-client-secret"
requiredRoles: "admin"
useTokenIntrospection: true
introspectionCacheEnabled: true
introspectionCacheTtl: 120 Introspection Options
| Option | Default | Description |
|---|---|---|
| false | Enable OAuth 2.0 token introspection. When enabled, tokens are validated via Keycloak’s introspection endpoint instead of local JWT parsing. |
| true | Enable caching of introspection results to reduce API calls to Keycloak. Highly recommended for production use. |
| 60 | Time-to-live for cached introspection results in seconds. Balance between security (lower TTL) and performance (higher TTL). |
| Token introspection requires a confidential client with client credentials (client ID and client secret). |
Local JWT vs. Token Introspection
| Feature | Local JWT Parsing | Token Introspection |
|---|---|---|
Performance | ⚡ Very Fast (local) | 🔄 Network call required |
Revocation Detection | ❌ No | ✅ Yes (real-time) |
Offline Support | ✅ Yes | ❌ Requires Keycloak connectivity |
Caching | N/A | ✅ Configurable TTL |
Security | Good | Excellent |
Best For | High-throughput, trusted environments | Security-critical operations |
Usage Examples
Security-Critical Endpoints
Use introspection for endpoints that require the highest level of security:
-
Java
-
YAML
// High security policy with introspection
KeycloakSecurityPolicy paymentPolicy = new KeycloakSecurityPolicy();
paymentPolicy.setServerUrl("http://localhost:8080");
paymentPolicy.setRealm("production-realm");
paymentPolicy.setClientId("payment-service");
paymentPolicy.setClientSecret("payment-secret");
paymentPolicy.setRequiredRoles("payment-processor");
// Enable introspection for payment endpoints
paymentPolicy.setUseTokenIntrospection(true);
paymentPolicy.setIntrospectionCacheEnabled(true);
paymentPolicy.setIntrospectionCacheTtl(30); // Short TTL for security
// Standard policy with local JWT parsing for regular endpoints
KeycloakSecurityPolicy standardPolicy = new KeycloakSecurityPolicy();
standardPolicy.setServerUrl("http://localhost:8080");
standardPolicy.setRealm("production-realm");
standardPolicy.setClientId("api-service");
standardPolicy.setClientSecret("api-secret");
standardPolicy.setRequiredRoles("user");
// useTokenIntrospection is false by default (local JWT parsing)
// Apply stricter validation to payment endpoints
from("rest:post:/payments/process")
.policy(paymentPolicy)
.to("bean:paymentService?method=processPayment");
// Use faster local validation for regular API calls
from("rest:get:/api/data")
.policy(standardPolicy)
.to("bean:dataService?method=getData"); # Payment endpoint with introspection
- route:
from:
uri: rest:post:/payments/process
steps:
- policy:
ref: paymentPolicy
- to:
uri: bean:paymentService?method=processPayment
# Regular endpoint with local JWT
- route:
from:
uri: rest:get:/api/data
steps:
- policy:
ref: standardPolicy
- to:
uri: bean:dataService?method=getData
# Security policies
beans:
- name: paymentPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "production-realm"
clientId: "payment-service"
clientSecret: "payment-secret"
requiredRoles: "payment-processor"
useTokenIntrospection: true
introspectionCacheEnabled: true
introspectionCacheTtl: 30
- name: standardPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "production-realm"
clientId: "api-service"
clientSecret: "api-secret"
requiredRoles: "user" Hybrid Approach - Different Policies for Different Endpoints
-
Java
-
YAML
public class HybridSecurityRoutes extends RouteBuilder {
@Override
public void configure() throws Exception {
// Introspection for admin operations
KeycloakSecurityPolicy adminIntrospection = new KeycloakSecurityPolicy(
"{{keycloak.server-url}}", "{{keycloak.realm}}",
"{{keycloak.client-id}}", "{{keycloak.client-secret}}");
adminIntrospection.setRequiredRoles("admin");
adminIntrospection.setUseTokenIntrospection(true);
adminIntrospection.setIntrospectionCacheTtl(60);
// Local JWT for read operations (faster)
KeycloakSecurityPolicy readPolicy = new KeycloakSecurityPolicy(
"{{keycloak.server-url}}", "{{keycloak.realm}}",
"{{keycloak.client-id}}", "{{keycloak.client-secret}}");
readPolicy.setRequiredRoles("reader,user");
readPolicy.setAllRolesRequired(false);
// useTokenIntrospection = false (default, uses local JWT)
// Admin routes - use introspection for security
from("rest:delete:/users/{id}")
.policy(adminIntrospection)
.to("bean:userService?method=deleteUser");
from("rest:post:/roles/assign")
.policy(adminIntrospection)
.to("bean:roleService?method=assignRole");
// Read routes - use local JWT for performance
from("rest:get:/users")
.policy(readPolicy)
.to("bean:userService?method=listUsers");
from("rest:get:/profile")
.policy(readPolicy)
.to("bean:userService?method=getProfile");
}
} # Admin routes with introspection
- route:
from:
uri: rest:delete:/users/{id}
steps:
- policy:
ref: adminIntrospection
- to:
uri: bean:userService?method=deleteUser
- route:
from:
uri: rest:post:/roles/assign
steps:
- policy:
ref: adminIntrospection
- to:
uri: bean:roleService?method=assignRole
# Read routes with local JWT
- route:
from:
uri: rest:get:/users
steps:
- policy:
ref: readPolicy
- to:
uri: bean:userService?method=listUsers
- route:
from:
uri: rest:get:/profile
steps:
- policy:
ref: readPolicy
- to:
uri: bean:userService?method=getProfile
# Security policies
beans:
- name: adminIntrospection
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "admin"
useTokenIntrospection: true
introspectionCacheTtl: 60
- name: readPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "reader,user"
allRolesRequired: false Performance Tuning
Cache Configuration
The introspection cache significantly reduces the performance impact of API calls to Keycloak:
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
policy.setServerUrl("http://localhost:8080");
policy.setRealm("my-realm");
policy.setClientId("my-client");
policy.setClientSecret("my-client-secret");
policy.setUseTokenIntrospection(true);
// Fine-tune cache settings based on your requirements
policy.setIntrospectionCacheEnabled(true);
// High-security environment: shorter TTL
policy.setIntrospectionCacheTtl(30); // 30 seconds
// High-performance environment: longer TTL
// policy.setIntrospectionCacheTtl(300); // 5 minutes
// Disable cache for maximum security (not recommended for production)
// policy.setIntrospectionCacheEnabled(false); Cache TTL Recommendations
| Use Case | Recommended TTL | Rationale |
|---|---|---|
High-Security (e.g., payments) | 30-60 seconds | Minimizes window for revoked token use |
Standard API | 60-120 seconds | Balances security and performance |
High-Throughput | 120-300 seconds | Reduces Keycloak load |
Internal Services | 300-600 seconds | Trusted environment, prioritize performance |
| When a token is introspected and cached, subsequent requests with the same token will use the cached result until the TTL expires. Balance security requirements with performance needs. |
Pluggable Cache Implementation
The token introspection feature supports pluggable cache implementations for flexible caching strategies. This allows you to choose the best caching solution for your performance and scalability requirements.
Available Cache Types
The component provides three cache implementations:
-
ConcurrentMap Cache (default) - Simple in-memory cache using
ConcurrentHashMap-
Time-based expiration (TTL)
-
No external dependencies
-
Suitable for basic use cases and backward compatibility
-
-
Caffeine Cache (recommended for production) - High-performance cache with advanced features
-
Time-based expiration
-
Size-based eviction
-
Detailed statistics (hit rate, miss rate, evictions)
-
Optimized for high throughput
-
-
No Cache - Disables caching completely
-
Every token is introspected on each request
-
Useful for testing or strict security requirements
-
Using Caffeine Cache
-
Java
import org.apache.camel.component.keycloak.security.cache.TokenCacheType;
// Create introspector with Caffeine cache for high performance
KeycloakTokenIntrospector introspector = new KeycloakTokenIntrospector(
serverUrl,
realm,
clientId,
clientSecret,
TokenCacheType.CAFFEINE,
300, // TTL in seconds (5 minutes)
10000, // max cache size (0 for unlimited)
true // record statistics
);
// Check cache statistics
TokenCache.CacheStats stats = introspector.getCacheStats();
if (stats != null) {
System.out.println("Cache hit rate: " + stats.getHitRate());
System.out.println("Total hits: " + stats.getHitCount());
System.out.println("Total misses: " + stats.getMissCount());
System.out.println("Evictions: " + stats.getEvictionCount());
}
// Get current cache size
long size = introspector.getCacheSize();
System.out.println("Cached tokens: " + size); Custom Cache Configuration
-
Java
import org.apache.camel.component.keycloak.security.cache.TokenCache;
import org.apache.camel.component.keycloak.security.cache.CaffeineTokenCache;
// Create a custom cache with specific settings
TokenCache customCache = new CaffeineTokenCache(
600, // 10 minutes TTL
50000, // max 50k entries
true // enable stats
);
// Use the custom cache instance
KeycloakTokenIntrospector introspector = new KeycloakTokenIntrospector(
serverUrl,
realm,
clientId,
clientSecret,
customCache
); Cache Configuration in Security Policy
-
Java
-
YAML
// High-performance policy with Caffeine cache
KeycloakSecurityPolicy highPerfPolicy = new KeycloakSecurityPolicy();
highPerfPolicy.setServerUrl("http://localhost:8080");
highPerfPolicy.setRealm("production-realm");
highPerfPolicy.setClientId("api-service");
highPerfPolicy.setClientSecret("api-secret");
highPerfPolicy.setRequiredRoles("user");
// Enable introspection with Caffeine cache
highPerfPolicy.setUseTokenIntrospection(true);
highPerfPolicy.setIntrospectionCacheType(TokenCacheType.CAFFEINE);
highPerfPolicy.setIntrospectionCacheTtl(120); // 2 minutes
highPerfPolicy.setIntrospectionCacheMaxSize(5000); // max 5k tokens
highPerfPolicy.setIntrospectionCacheStats(true); // enable statistics
from("rest:get:/api/data")
.policy(highPerfPolicy)
.to("bean:dataService?method=getData"); # Bean definition with Caffeine cache
beans:
- name: highPerfPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "production-realm"
clientId: "api-service"
clientSecret: "api-secret"
requiredRoles: "user"
useTokenIntrospection: true
introspectionCacheType: "CAFFEINE"
introspectionCacheTtl: 120
introspectionCacheMaxSize: 5000
introspectionCacheStats: true Cache Performance Monitoring
For production systems, monitor cache performance to optimize TTL and size settings:
// Periodically check cache performance
TokenCache.CacheStats stats = introspector.getCacheStats();
if (stats != null) {
logger.info("Token cache performance: " + stats.toString());
// Output: CacheStats{hits=1000, misses=100, hitRate=90.91%, evictions=50}
// Alert if hit rate is too low
if (stats.getHitRate() < 0.7) {
logger.warn("Low cache hit rate: " + stats.getHitRate());
logger.warn("Consider increasing cache TTL or size");
}
// Monitor evictions
if (stats.getEvictionCount() > threshold) {
logger.info("High eviction count: " + stats.getEvictionCount());
logger.info("Consider increasing cache max size");
}
} Cache Selection Guidelines
| Use Case | Recommended Cache | Configuration |
|---|---|---|
Development/Testing | ConcurrentMap | TTL: 60s, Default settings |
Production (Low-Medium Traffic) | ConcurrentMap | TTL: 120s, Monitor size |
Production (High Traffic) | Caffeine | TTL: 300s, maxSize: 10000+, stats enabled |
Very High Traffic | Caffeine | TTL: 300s, maxSize: 50000+, stats enabled |
Strict Security | NONE or low TTL | TTL: 30s or disable caching |
Testing/Debugging | NONE | Disable caching for fresh validation |
Best Practices
-
TTL Configuration: Set TTL slightly shorter than your token expiration time to balance security and performance
-
Cache Size: For Caffeine, set
maxSizebased on expected concurrent users:-
Small deployments (< 1000 users): 1000-5000 entries
-
Medium deployments (1000-10000 users): 10000-25000 entries
-
Large deployments (> 10000 users): 50000+ entries
-
-
Statistics: Enable statistics in production environments for monitoring and tuning
-
Cleanup: Always call
introspector.close()when shutting down to release resources properly -
Different Policies for Different Endpoints: Use Caffeine cache for high-traffic endpoints and stricter settings for security-critical endpoints
// High-traffic endpoint - use Caffeine with longer TTL
KeycloakSecurityPolicy highTrafficPolicy = new KeycloakSecurityPolicy();
highTrafficPolicy.setUseTokenIntrospection(true);
highTrafficPolicy.setIntrospectionCacheType(TokenCacheType.CAFFEINE);
highTrafficPolicy.setIntrospectionCacheTtl(300); // 5 minutes
// Security-critical endpoint - shorter TTL or no cache
KeycloakSecurityPolicy criticalPolicy = new KeycloakSecurityPolicy();
criticalPolicy.setUseTokenIntrospection(true);
criticalPolicy.setIntrospectionCacheType(TokenCacheType.NONE); // No caching
from("rest:get:/api/users")
.policy(highTrafficPolicy) // Cached validation
.to("bean:userService");
from("rest:post:/payments")
.policy(criticalPolicy) // Real-time validation
.to("bean:paymentService"); Token Revocation Workflow
When using token introspection, you can implement a complete token revocation workflow:
// 1. Revoke token in Keycloak (via admin API)
template.sendBodyAndHeader("keycloak:admin?operation=revokeSession", null,
KeycloakConstants.SESSION_ID, sessionId);
// 2. Subsequent requests with the revoked token will be rejected
// The introspection endpoint will return "active": false
// The security policy will throw CamelAuthorizationException
// 3. Handle revoked token gracefully
onException(CamelAuthorizationException.class)
.handled(true)
.log("Token validation failed: ${exception.message}")
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(401))
.transform().constant("{\"error\": \"Token has been revoked\"}"); Best Practices
-
Use Introspection Selectively: Enable introspection for security-critical endpoints while using local JWT parsing for high-throughput APIs.
-
Enable Caching: Always enable caching in production to reduce load on Keycloak and improve performance.
-
Tune Cache TTL: Set appropriate cache TTL based on your security requirements:
-
Lower TTL (30-60s) for security-critical operations
-
Higher TTL (2-5min) for regular operations
-
-
Monitor Performance: Track introspection API call latency and adjust caching accordingly.
-
Handle Failures Gracefully: Implement proper error handling for network failures or Keycloak downtime.
-
Use Circuit Breakers: Consider implementing circuit breakers for the introspection endpoint to handle Keycloak unavailability.
Security Considerations
-
Client Secret Protection: Introspection requires a client secret. Store it securely (environment variables, vault services).
-
HTTPS in Production: Always use HTTPS for introspection requests in production.
-
Keycloak Availability: Introspection requires Keycloak to be available. Plan for high availability.
-
Rate Limiting: Be aware of Keycloak’s rate limits for introspection endpoints.
-
Token Leakage: Even with introspection, protect tokens from leakage as they remain valid until revoked.
Troubleshooting
High Latency: - Enable caching if disabled - Increase cache TTL - Check network latency to Keycloak - Consider using local JWT for non-critical endpoints
Cache Not Working: - Verify introspectionCacheEnabled is set to true - Check that TTL is > 0 - Ensure tokens are identical (including whitespace)
Introspection Failures: - Verify client secret is correct - Ensure client has introspection permissions - Check Keycloak logs for errors - Verify network connectivity to Keycloak
Resource Owner Password Credentials
-
Java
-
YAML
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
"http://localhost:8080", "my-realm", "my-client", "username", "password");
from("direct:user-flow")
.policy(policy)
.to("mock:result"); - route:
from:
uri: direct:user-flow
steps:
- policy:
ref: userFlowPolicy
- to:
uri: mock:result
# Bean definition
beans:
- name: userFlowPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "my-realm"
clientId: "my-client"
username: "username"
password: "password"
useResourceOwnerPasswordCredentials: true Usage
Providing Access Tokens
The security policy expects access tokens to be provided in one of the following ways:
-
Header:
CamelKeycloakAccessToken -
Authorization Header:
Authorization: Bearer <token> -
Exchange Property:
CamelKeycloakAccessToken
// Using header
template.sendBodyAndHeader("direct:protected", "message",
KeycloakSecurityConstants.ACCESS_TOKEN_HEADER, accessToken);
// Using Authorization header
template.sendBodyAndHeader("direct:protected", "message",
"Authorization", "Bearer " + accessToken); Route Examples
-
Java
-
YAML
from("direct:admin-only")
.policy(adminPolicy)
.transform().constant("Admin access granted")
.to("mock:admin");
from("direct:user-or-admin")
.policy(userPolicy)
.transform().constant("User access granted")
.to("mock:user");
from("rest:get:/api/documents")
.policy(documentsPolicy)
.to("direct:list-documents"); - route:
from:
uri: direct:admin-only
steps:
- policy:
ref: adminPolicy
- transform:
constant: "Admin access granted"
- to:
uri: mock:admin
- route:
from:
uri: direct:user-or-admin
steps:
- policy:
ref: userPolicy
- transform:
constant: "User access granted"
- to:
uri: mock:user
- rest:
get:
- uri: /api/documents
to: direct:list-documents
route:
policy:
ref: documentsPolicy Configuration Options
| Name | Default | Description |
|---|---|---|
serverUrl | Keycloak server URL (e.g., http://localhost:8080) | |
realm | Keycloak realm name | |
clientId | Keycloak client ID | |
clientSecret | Keycloak client secret (for client credentials flow) | |
username | Username (for resource owner password flow) | |
password | Password (for resource owner password flow) | |
requiredRoles | "" | Comma-separated list of required roles (e.g., "admin,user,manager") |
requiredPermissions | "" | Comma-separated list of required permissions (e.g., "read:documents,write:documents") |
allRolesRequired | true | Whether ALL roles are required (true) or ANY role (false) |
allPermissionsRequired | true | Whether ALL permissions are required (true) or ANY permission (false) |
useResourceOwnerPasswordCredentials | false | Whether to use resource owner password flow |
Security Considerations
-
Always use HTTPS in production environments
-
Store client secrets securely (environment variables, secret management systems)
-
Regularly rotate client secrets and user passwords
-
Use the principle of least privilege when assigning roles and permissions
-
Consider token expiration and refresh strategies
Error Handling
The component throws CamelAuthorizationException when:
-
Access token is missing or invalid
-
User doesn’t have required roles
-
User doesn’t have required permissions
-
Keycloak server is unreachable
-
Token verification fails
-
Java
-
YAML
onException(CamelAuthorizationException.class)
.handled(true)
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.transform().constant("Access denied"); - onException:
exception:
- "org.apache.camel.CamelAuthorizationException"
handled: true
steps:
- setHeader:
name: "CamelHttpResponseCode"
constant: 403
- transform:
constant: "Access denied" Examples
Basic Role-based Authorization
-
Java
-
YAML
// Create Keycloak security policy
KeycloakSecurityPolicy keycloakPolicy = new KeycloakSecurityPolicy();
keycloakPolicy.setServerUrl("http://localhost:8080");
keycloakPolicy.setRealm("my-company");
keycloakPolicy.setClientId("my-service");
keycloakPolicy.setClientSecret("client-secret-value");
// Require admin role (comma-separated string)
keycloakPolicy.setRequiredRoles("admin");
// Apply to route
from("direct:admin-endpoint")
.policy(keycloakPolicy)
.transform().constant("Admin access granted")
.to("mock:admin-result"); - route:
from:
uri: direct:admin-endpoint
steps:
- policy:
ref: keycloakPolicy
- transform:
constant: "Admin access granted"
- to:
uri: mock:admin-result
# Bean definition
beans:
- name: keycloakPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "my-company"
clientId: "my-service"
clientSecret: "client-secret-value"
requiredRoles: "admin" Multiple Role Authorization
-
Java
-
YAML
// Require either admin OR user role (comma-separated string)
KeycloakSecurityPolicy userPolicy = new KeycloakSecurityPolicy(
"http://localhost:8080", "my-company", "my-service", "client-secret");
userPolicy.setRequiredRoles("admin,user");
userPolicy.setAllRolesRequired(false); // ANY role (OR logic)
from("direct:user-endpoint")
.policy(userPolicy)
.to("bean:userService?method=processUser"); - route:
from:
uri: direct:user-endpoint
steps:
- policy:
ref: userPolicy
- to:
uri: bean:userService?method=processUser
# Bean definition
beans:
- name: userPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "http://localhost:8080"
realm: "my-company"
clientId: "my-service"
clientSecret: "client-secret"
requiredRoles: "admin,user"
allRolesRequired: false REST API with Keycloak Protection
-
Java
-
YAML
// Configure different policies for different endpoints
KeycloakSecurityPolicy readPolicy = new KeycloakSecurityPolicy(
"{{keycloak.server-url}}", "{{keycloak.realm}}",
"{{keycloak.client-id}}", "{{keycloak.client-secret}}");
readPolicy.setRequiredRoles("reader,writer,admin");
readPolicy.setAllRolesRequired(false);
KeycloakSecurityPolicy writePolicy = new KeycloakSecurityPolicy(
"{{keycloak.server-url}}", "{{keycloak.realm}}",
"{{keycloak.client-id}}", "{{keycloak.client-secret}}");
writePolicy.setRequiredRoles("writer,admin");
writePolicy.setAllRolesRequired(false);
KeycloakSecurityPolicy adminPolicy = new KeycloakSecurityPolicy(
"{{keycloak.server-url}}", "{{keycloak.realm}}",
"{{keycloak.client-id}}", "{{keycloak.client-secret}}");
adminPolicy.setRequiredRoles("admin");
// Configure REST endpoints
rest("/api")
.get("/documents")
.route()
.policy(readPolicy)
.to("bean:documentService?method=listDocuments")
.endRest()
.post("/documents")
.route()
.policy(writePolicy)
.to("bean:documentService?method=createDocument")
.endRest()
.delete("/documents/{id}")
.route()
.policy(adminPolicy)
.to("bean:documentService?method=deleteDocument")
.endRest(); - rest:
path: "/api"
get:
- uri: "/documents"
to: bean:documentService?method=listDocuments
route:
policy:
ref: readPolicy
post:
- uri: "/documents"
to: bean:documentService?method=createDocument
route:
policy:
ref: writePolicy
delete:
- uri: "/documents/{id}"
to: bean:documentService?method=deleteDocument
route:
policy:
ref: adminPolicy
# Bean definitions for policies
beans:
- name: readPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "reader,writer,admin"
allRolesRequired: false
- name: writePolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "writer,admin"
allRolesRequired: false
- name: adminPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "admin" Sending Requests with Tokens
// In your client code, include the access token
String accessToken = "eyJhbGciOiJSUzI1NiIsInR5cC..."; // From Keycloak
// Option 1: Using custom header
template.sendBodyAndHeader("direct:protected-endpoint",
requestBody,
KeycloakSecurityConstants.ACCESS_TOKEN_HEADER,
accessToken);
// Option 2: Using Authorization header (standard)
template.sendBodyAndHeader("direct:protected-endpoint",
requestBody,
"Authorization",
"Bearer " + accessToken);
// Option 3: Using exchange property
Exchange exchange = ExchangeBuilder.anExchange(camelContext)
.withBody(requestBody)
.withProperty(KeycloakSecurityConstants.ACCESS_TOKEN_PROPERTY, accessToken)
.build();
template.send("direct:protected-endpoint", exchange); Advanced Error Handling
-
Java
-
YAML
// Global error handler for authorization failures
onException(CamelAuthorizationException.class)
.handled(true)
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.setHeader("Content-Type", constant("application/json"))
.transform().constant("{\"error\": \"Access denied\", \"message\": \"Insufficient privileges\"}")
.log("Authorization failed: ${exception.message}");
// Route-specific error handling
from("rest:post:/secure-data")
.doTry()
.policy(keycloakPolicy)
.to("bean:dataProcessor")
.doCatch(CamelAuthorizationException.class)
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.transform().constant("Access denied")
.end(); # Global error handler
- onException:
exception:
- "org.apache.camel.CamelAuthorizationException"
handled: true
steps:
- setHeader:
name: "CamelHttpResponseCode"
constant: 403
- setHeader:
name: "Content-Type"
constant: "application/json"
- transform:
constant: '{"error": "Access denied", "message": "Insufficient privileges"}'
- log: "Authorization failed: ${exception.message}"
# Route-specific error handling
- route:
from:
uri: rest:post:/secure-data
steps:
- doTry:
steps:
- policy:
ref: keycloakPolicy
- to:
uri: bean:dataProcessor
doCatch:
- exception:
- "org.apache.camel.CamelAuthorizationException"
steps:
- setHeader:
name: "CamelHttpResponseCode"
constant: 403
- transform:
constant: "Access denied" Configuration Properties
# application.properties
keycloak.server-url=http://localhost:8080
keycloak.realm=my-company
keycloak.client-id=my-service
keycloak.client-secret=your-client-secret Spring Configuration
@Configuration
public class SecurityConfiguration {
@Value("${keycloak.server-url}")
private String serverUrl;
@Value("${keycloak.realm}")
private String realm;
@Value("${keycloak.client-id}")
private String clientId;
@Value("${keycloak.client-secret}")
private String clientSecret;
@Bean
public KeycloakSecurityPolicy adminPolicy() {
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
serverUrl, realm, clientId, clientSecret);
policy.setRequiredRoles("admin");
return policy;
}
@Bean
public KeycloakSecurityPolicy userPolicy() {
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
serverUrl, realm, clientId, clientSecret);
policy.setRequiredRoles("user,admin");
policy.setAllRolesRequired(false);
return policy;
}
} Complete Route Configuration
-
Java
-
YAML
public class KeycloakSecurityRoutes extends RouteBuilder {
@Override
public void configure() throws Exception {
// Admin policy - requires admin role
KeycloakSecurityPolicy adminPolicy = new KeycloakSecurityPolicy(
"{{keycloak.server-url}}", "{{keycloak.realm}}",
"{{keycloak.client-id}}", "{{keycloak.client-secret}}");
adminPolicy.setRequiredRoles("admin");
// User policy - requires user or admin role
KeycloakSecurityPolicy userPolicy = new KeycloakSecurityPolicy(
"{{keycloak.server-url}}", "{{keycloak.realm}}",
"{{keycloak.client-id}}", "{{keycloak.client-secret}}");
userPolicy.setRequiredRoles("user,admin");
userPolicy.setAllRolesRequired(false); // ANY role
// Error handling
onException(CamelAuthorizationException.class)
.handled(true)
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.transform().constant("Forbidden");
// Routes
from("rest:get:/admin/users")
.policy(adminPolicy)
.to("bean:userService?method=getAllUsers");
from("rest:get:/profile")
.policy(userPolicy)
.to("bean:userService?method=getCurrentUser");
}
} # Complete route configuration with Keycloak security
- onException:
exception:
- "org.apache.camel.CamelAuthorizationException"
handled: true
steps:
- setHeader:
name: "CamelHttpResponseCode"
constant: 403
- transform:
constant: "Forbidden"
- route:
from:
uri: rest:get:/admin/users
steps:
- policy:
ref: adminPolicy
- to:
uri: bean:userService?method=getAllUsers
- route:
from:
uri: rest:get:/profile
steps:
- policy:
ref: userPolicy
- to:
uri: bean:userService?method=getCurrentUser
# Security policy beans
beans:
- name: adminPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "admin"
- name: userPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "user,admin"
allRolesRequired: false Running Integration Tests
The component includes integration tests that require a running Keycloak instance. These tests are disabled by default and only run when specific system properties are provided.
The integration tests include comprehensive testing for: * Role-based authorization with different role requirements * Permission-based authorization using custom claims and scopes * Public key verification with JWKS endpoint integration * Combined roles and permissions validation * Token parsing with and without public key verification * Different authorization header formats (Bearer token, custom header) * Token expiration and validity checks * Error handling for invalid tokens and insufficient privileges
Starting Keycloak with Docker
1. Start Keycloak Container
# Start Keycloak in development mode
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:latest start-dev 2. Access Keycloak Admin Console
Open your browser to http://localhost:8080/admin and login with: - Username: admin - Password: admin
Keycloak Configuration for Integration Tests
3. Create Test Realm
-
In the Keycloak Admin Console, click "Add realm"
-
Set realm name to:
test-realm -
Click "Create"
4. Create Test Client
-
In the
test-realm, go to Clients → "Create client" -
Set the following:
-
Client type:
OpenID Connect -
Client ID:
test-client -
Next → Client authentication:
ON -
Authorization:
ON(optional, for advanced features) -
Next → Valid redirect URIs:
* -
Click "Save"
-
-
Go to Credentials tab and copy the Client Secret
5. Create Test Roles
-
Go to Realm roles → "Create role"
-
Create the following roles:
-
admin-role -
user -
reader
-
6. Create Test Users
Create three test users with the following configuration:
User 1: myuser 1. Go to Users → "Add user" 2. Set: - Username: myuser - Email: myuser@test.com - First name: My - Last name: User - Click "Create" 3. Go to Credentials tab → "Set password" - Password: pippo123 - Temporary: OFF 4. Go to Role mapping tab → "Assign role" - Assign role: admin-role
User 2: test-user 1. Create user with: - Username: test-user - Password: user123 (temporary: OFF) - Assign role: user
User 3: reader-user 1. Create user with: - Username: reader-user - Password: reader123 (temporary: OFF) - Assign role: reader
Running the Integration Tests
7. Execute Tests with Maven
Run All Integration Tests:
# Run integration tests with required properties
mvn test -Dtest=KeycloakSecurityIT \
-Dkeycloak.server.url=http://localhost:8080 \
-Dkeycloak.realm=test-realm \
-Dkeycloak.client.id=test-client \
-Dkeycloak.client.secret=YOUR_CLIENT_SECRET Run Specific Test Categories:
# Test only role-based authorization
mvn test -Dtest=KeycloakSecurityIT#testKeycloakSecurityPolicyWithValidAdminToken,testKeycloakSecurityPolicyWithValidUserToken,testKeycloakSecurityPolicyUserCannotAccessAdminRoute \
-Dkeycloak.server.url=http://localhost:8080 \
-Dkeycloak.realm=test-realm \
-Dkeycloak.client.id=test-client \
-Dkeycloak.client.secret=YOUR_CLIENT_SECRET
# Test only permissions-based authorization
mvn test -Dtest=KeycloakSecurityIT#testKeycloakSecurityPolicyWithPermissions,testKeycloakSecurityPolicyWithScopeBasedPermissions,testKeycloakSecurityPolicyWithCombinedRolesAndPermissions \
-Dkeycloak.server.url=http://localhost:8080 \
-Dkeycloak.realm=test-realm \
-Dkeycloak.client.id=test-client \
-Dkeycloak.client.secret=YOUR_CLIENT_SECRET
# Test only public key verification
mvn test -Dtest=KeycloakSecurityIT#testKeycloakSecurityPolicyWithPublicKeyVerification,testParseTokenDirectlyWithPublicKey \
-Dkeycloak.server.url=http://localhost:8080 \
-Dkeycloak.realm=test-realm \
-Dkeycloak.client.id=test-client \
-Dkeycloak.client.secret=YOUR_CLIENT_SECRET Replace YOUR_CLIENT_SECRET with the actual client secret from step 4.
Run Manual Producer Tests:
The KeycloakProducerIT test contains manual integration tests for producer operations. These tests are disabled by default and require explicit activation:
# Run manual producer integration tests
mvn test -Dtest=KeycloakProducerIT \
-Dmanual.keycloak.test=true \
-Dkeycloak.server.url=http://localhost:8080 \
-Dkeycloak.realm=master \
-Dkeycloak.username=admin \
-Dkeycloak.password=admin The -Dmanual.keycloak.test=true flag is required to run KeycloakProducerIT tests. Without this flag, the tests will be skipped even if other Keycloak properties are provided. This prevents the tests from accidentally running in automated CI environments. |
8. Alternative: Set Environment Variables
# For KeycloakSecurityIT tests
export KEYCLOAK_SERVER_URL=http://localhost:8080
export KEYCLOAK_REALM=test-realm
export KEYCLOAK_CLIENT_ID=test-client
export KEYCLOAK_CLIENT_SECRET=YOUR_CLIENT_SECRET
# Run security tests
mvn test -Dtest=KeycloakSecurityIT \
-Dkeycloak.server.url=$KEYCLOAK_SERVER_URL \
-Dkeycloak.realm=$KEYCLOAK_REALM \
-Dkeycloak.client.id=$KEYCLOAK_CLIENT_ID \
-Dkeycloak.client.secret=$KEYCLOAK_CLIENT_SECRET
# For manual producer tests (KeycloakProducerIT)
export KEYCLOAK_SERVER_URL=http://localhost:8080
export KEYCLOAK_REALM=master
export KEYCLOAK_USERNAME=admin
export KEYCLOAK_PASSWORD=admin
# Run manual producer tests (requires explicit flag)
mvn test -Dtest=KeycloakProducerIT \
-Dmanual.keycloak.test=true \
-Dkeycloak.server.url=$KEYCLOAK_SERVER_URL \
-Dkeycloak.realm=$KEYCLOAK_REALM \
-Dkeycloak.username=$KEYCLOAK_USERNAME \
-Dkeycloak.password=$KEYCLOAK_PASSWORD Troubleshooting
Tests are skipped: - For KeycloakSecurityIT: Verify all four required properties are provided and Keycloak is running on the specified URL. - For KeycloakProducerIT: Ensure -Dmanual.keycloak.test=true is set along with the required Keycloak properties.
401 Unauthorized: Check that: - Users exist with correct passwords - Users have the required roles assigned - Client credentials are correct
Connection refused: Ensure Keycloak is running and accessible at the specified URL.
Token validation errors: Verify the realm name and client configuration match exactly.
Setting up Permissions in Keycloak
For permissions-based authorization, you have several options to include permissions in tokens:
Option 1: Custom Claims Mapper
-
In your realm, go to Client Scopes → roles → Mappers → Create mapper
-
Set the following:
-
Mapper Type:
User Attribute -
Name:
permissions-mapper -
User Attribute:
permissions -
Token Claim Name:
permissions -
Claim JSON Type:
JSON -
Add to ID token:
ON -
Add to access token:
ON
-
-
Add the
permissionsattribute to users:-
Go to Users → Select user → Attributes tab
-
Add attribute:
permissionswith value like["read:documents", "write:documents"]
-
Option 2: Scope-based Permissions
-
Configure client scopes:
-
Go to Client Scopes → Create client scope
-
Scope Name:
documents -
Protocol:
openid-connect
-
-
Add scope to client:
-
Go to Clients → Your client → Client Scopes tab
-
Add the scope as Default or Optional
-
-
In your application code, you can then use scopes as permissions:
KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
policy.setRequiredPermissions("documents,users,admin");
policy.setAllPermissionsRequired(false); // ANY permission Option 3: Authorization Services (Advanced)
For complex permission models, enable Keycloak Authorization Services:
-
Go to Clients → Your client → Settings → Authorization Enabled:
ON -
Configure Resources, Scopes, and Policies in the Authorization tab
-
Enable Authorization on the client
Note: Full Authorization Services integration requires additional setup and is more complex than the simple approaches above.
Combined Roles and Permissions Example
-
Java
-
YAML
// Create a policy that requires BOTH roles AND permissions
KeycloakSecurityPolicy strictPolicy = new KeycloakSecurityPolicy();
strictPolicy.setServerUrl("{{keycloak.server-url}}");
strictPolicy.setRealm("{{keycloak.realm}}");
strictPolicy.setClientId("{{keycloak.client-id}}");
strictPolicy.setClientSecret("{{keycloak.client-secret}}");
// User must have admin role AND document permissions (comma-separated)
strictPolicy.setRequiredRoles("admin");
strictPolicy.setRequiredPermissions("read:documents,write:documents");
strictPolicy.setAllRolesRequired(true);
strictPolicy.setAllPermissionsRequired(false); // ANY permission
// Create a policy that requires EITHER roles OR permissions
KeycloakSecurityPolicy flexiblePolicy = new KeycloakSecurityPolicy();
flexiblePolicy.setServerUrl("{{keycloak.server-url}}");
flexiblePolicy.setRealm("{{keycloak.realm}}");
flexiblePolicy.setClientId("{{keycloak.client-id}}");
flexiblePolicy.setClientSecret("{{keycloak.client-secret}}");
// Apply different policies to different routes
from("direct:admin-documents")
.policy(strictPolicy)
.to("bean:documentService?method=adminOperations");
from("direct:flexible-access")
.policy(flexiblePolicy)
.to("bean:documentService?method=flexibleOperations"); - route:
from:
uri: direct:admin-documents
steps:
- policy:
ref: strictPolicy
- to:
uri: bean:documentService?method=adminOperations
- route:
from:
uri: direct:flexible-access
steps:
- policy:
ref: flexiblePolicy
- to:
uri: bean:documentService?method=flexibleOperations
# Bean definitions
beans:
- name: strictPolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "admin"
requiredPermissions: "read:documents,write:documents"
allRolesRequired: true
allPermissionsRequired: false
- name: flexiblePolicy
type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
properties:
serverUrl: "{{keycloak.server-url}}"
realm: "{{keycloak.realm}}"
clientId: "{{keycloak.client-id}}"
clientSecret: "{{keycloak.client-secret}}"
requiredRoles: "admin,manager"
requiredPermissions: "read:documents,emergency:access"
allRolesRequired: false
allPermissionsRequired: false