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
,*.yaml
files, 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 30 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 | |
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 | ||
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 (46 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 | |
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 | ||
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);
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
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
- 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
-
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"); } }
- YAML
-
# 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
permissions
attribute to users:-
Go to Users → Select user → Attributes tab
-
Add attribute:
permissions
with 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