In this article, we will share how to parameter RedHat SSO Keycloak SPI adding a custom Event Listener module
1. Presentation
The goal of this article is to showcase the usage of SPI usage with keycloak. For this, it is illustrated with a very simple SPI example which an event listener.
2. Installing event Listener jar module
cd /keycloak_4.5/distribution/keycloak-4.6.0.Final-SNAPSHOT$ sh bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=~/keycloak_4.5/keycloak/examples/providers/event-listener-sysout/target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private"
2.1) you need to find out event-listener-sysout-example.jar
~/keycloak_4.5/keycloak/examples/providers/event-listener-sysout/target/event-listener-sysout-example.jar
command:
cd /keycloak_4.5/distribution/keycloak-4.6.0.Final-SNAPSHOT$ sh bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=~/keycloak_4.5/keycloak/examples/providers/event-listener-sysout/target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private"
2.2) adding event listener to keycloak providers
Goto standalone.xml and look-up for
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1"> <web-context>auth</web-context> <providers> <provider>classpath:${jboss.home.dir}/providers/*</provider> </providers> add to the provider list <provider>module:org.keycloak.examples.event-sysout</provider> <subsystem xmlns="urn:jboss:domain:keycloak-server:1.1"> <web-context>auth</web-context> <providers> <provider>classpath:${jboss.home.dir}/providers/*</provider> <provider>module:org.keycloak.examples.event-sysout</provider> </providers> <master-realm-name>master</master-realm-name>
2.3) restart keycloak server
sh standalone.sh
Check that event listener module has been loaded in the server.log file
orivat@asus:~/keycloak_4.5/distribution/keycloak-4.6.0.Final-SNAPSHOT/standalone/log$ grep event * server.log:| 325: <provider>module:org.keycloak.examples.event-sysout</provider>
2.4) Configure Event Listener in the corresponding Realm pannel
You need to select:
- Event Listener (Sysout here)
- and save the whole
Optionally you can also specify to save events in the database.
3) Using the event Listener Log
Each Login/logout is tracked in the log file
09:23:55,871 INFO [stdout] (default task-10) EVENT: type=LOGIN, realmId=master, clientId=security-admin-console, userId=67c30037-1147-4e72-acf4-bd79919f3072, ipAddress=127.0.0.1, auth_method=openid-connect, auth_type=code, redirect_uri=https://localhost:8080/auth/admin/master/console/#/realms/master/events-settings, consent=no_consent_required, code_id=7d829633-c724-4a71-8858-1e6b8502c021, username=admin 09:23:56,265 INFO [stdout] (default task-10) EVENT: type=CODE_TO_TOKEN, realmId=master, clientId=security-admin-console, userId=67c30037-1147-4e72-acf4-bd79919f3072, ipAddress=127.0.0.1, token_id=38d6820c-e9c0-4021-9364-214d18147fe5, grant_type=authorization_code, refresh_token_type=Refresh, scope='openid email profile', refresh_token_id=2b8ca89e-1654-421e-b593-54090d83b498, code_id=7d829633-c724-4a71-8858-1e6b8502c021, client_auth_method=client-secret 09:28:01,311 INFO [stdout] (default task-11) EVENT: type=LOGOUT, realmId=master, clientId=null, userId=67c30037-1147-4e72-acf4-bd79919f3072, ipAddress=127.0.0.1, redirect_uri=https://localhost:8080/auth/admin/master/console/#/realms/master/events-settings
4) Code of the event Listener SysoutEventListenerProvider.java
package org.keycloak.examples.providers.events; import org.keycloak.events.Event; import org.keycloak.events.EventListenerProvider; import org.keycloak.events.EventType; import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.OperationType; import java.util.Map; import java.util.Set; /** * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> */ public class SysoutEventListenerProvider implements EventListenerProvider { private Set<EventType> excludedEvents; private Set<OperationType> excludedAdminOperations; public SysoutEventListenerProvider(Set<EventType> excludedEvents, Set<OperationType> excludedAdminOpearations) { this.excludedEvents = excludedEvents; this.excludedAdminOperations = excludedAdminOpearations; } @Override public void onEvent(Event event) { // Ignore excluded events if (excludedEvents != null && excludedEvents.contains(event.getType())) { return; } else { System.out.println("EVENT: " + toString(event)); } } @Override public void onEvent(AdminEvent event, boolean includeRepresentation) { // Ignore excluded operations if (excludedAdminOperations != null && excludedAdminOperations.contains(event.getOperationType())) { return; } else { System.out.println("EVENT: " + toString(event)); } } private String toString(Event event) { StringBuilder sb = new StringBuilder(); sb.append("type="); sb.append(event.getType()); sb.append(", realmId="); sb.append(event.getRealmId()); sb.append(", clientId="); sb.append(event.getClientId()); sb.append(", userId="); sb.append(event.getUserId()); sb.append(", ipAddress="); sb.append(event.getIpAddress()); if (event.getError() != null) { sb.append(", error="); sb.append(event.getError()); } if (event.getDetails() != null) { for (Map.Entry<String, String> e : event.getDetails().entrySet()) { sb.append(", "); sb.append(e.getKey()); if (e.getValue() == null || e.getValue().indexOf(' ') == -1) { sb.append("="); sb.append(e.getValue()); } else { sb.append("='"); sb.append(e.getValue()); sb.append("'"); } } } return sb.toString(); } private String toString(AdminEvent adminEvent) { StringBuilder sb = new StringBuilder(); sb.append("operationType="); sb.append(adminEvent.getOperationType()); sb.append(", realmId="); sb.append(adminEvent.getAuthDetails().getRealmId()); sb.append(", clientId="); sb.append(adminEvent.getAuthDetails().getClientId()); sb.append(", userId="); sb.append(adminEvent.getAuthDetails().getUserId()); sb.append(", ipAddress="); sb.append(adminEvent.getAuthDetails().getIpAddress()); sb.append(", resourcePath="); sb.append(adminEvent.getResourcePath()); if (adminEvent.getError() != null) { sb.append(", error="); sb.append(adminEvent.getError()); } return sb.toString(); } @Override public void close() { } }
Code of SysoutEventListenerProviderFactory.java
/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.examples.providers.events; import org.keycloak.Config; import org.keycloak.events.EventListenerProvider; import org.keycloak.events.EventListenerProviderFactory; import org.keycloak.events.EventType; import org.keycloak.events.admin.OperationType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import java.util.HashSet; import java.util.Set; /** * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> */ public class SysoutEventListenerProviderFactory implements EventListenerProviderFactory { private Set<EventType> excludedEvents; private Set<OperationType> excludedAdminOperations; @Override public EventListenerProvider create(KeycloakSession session) { return new SysoutEventListenerProvider(excludedEvents, excludedAdminOperations); } @Override public void init(Config.Scope config) { String[] excludes = config.getArray("exclude-events"); if (excludes != null) { excludedEvents = new HashSet<>(); for (String e : excludes) { excludedEvents.add(EventType.valueOf(e)); } } String[] excludesOperations = config.getArray("excludesOperations"); if (excludesOperations != null) { excludedAdminOperations = new HashSet<>(); for (String e : excludesOperations) { excludedAdminOperations.add(OperationType.valueOf(e)); } } } @Override public void postInit(KeycloakSessionFactory factory) { } @Override public void close() { } @Override public String getId() { return "sysout"; } }
5) Pointers
pointer:
Adding Event Listener (official instructions)
To deploy copy target/event-listener-sysout-example.jar to providers directory. Alternatively you can deploy as a module by running: KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private" Then registering the provider by editing standalone/configuration/standalone.xml and adding the module to the providers element: <providers> ... <provider>module:org.keycloak.examples.event-sysout</provider> </providers> Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events, followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and login again to see events printed to System.out. The example event listener can be configured to exclude certain events, for example to exclude REFRESH_TOKEN and CODE_TO_TOKEN events add the following to standalone.xml: ... <spi name="eventsListener"> <provider name="sysout"> <properties> <property name="exclude-events" value="["REFRESH_TOKEN", "CODE_TO_TOKEN"]"/> </properties> </provider </spi>
- New Keycloak online training - 19 janvier 2022
- Sizing Keycloak or Redhat SSO projects - 8 juin 2021
- Keycloak.X Distribution - 28 janvier 2021