Je continue le sujet de mon collègue sur Keycloak.
Qui n'a pas besoin d'eau, mais juste d'un exemple de code, sautez ici .
Keycloak est assez souvent utilisé comme solution de gestion des identités et des accès pour les applications modernes au sein des applications d'entreprise.
Keycloak est écrit dans le langage Java, et les créateurs ont initialement présenté une opportunité très pratique d'étendre les fonctionnalités de la solution prête à l'emploi avec ce que l'on appelle des add-ons ou officiellement: des extensions .
Une extension est un projet Java standard composé de classes qui étendent les classes / interfaces Keycloak par défaut avec des fonctionnalités supplémentaires requises. De plus, vous pouvez étendre les fonctionnalités de presque toutes les classes Keycloak et dans n'importe quel but: d'une modification minime du texte d'un message concernant un utilisateur incorrect entré un mot de passe, à la liaison d'un Discord , en tant que fournisseur d'identité .
Cet article se concentrera sur l'extension de l'écouteur d'événements par défaut dans Keycloak.
: .
Java . Maven. pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>event-listener-keycloak-extension</artifactId>
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>12.0.4</version>
</parent>
<properties>
<keycloak.version>12.0.4</keycloak.version>
<lombok.version>1.18.20</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core-public</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>event-listener-keycloak-extension</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Lombok. .
, :
EventListenerProvider
. . .
EventListenerProviderFactory
.EventListenerProvider
.EventListenerProvider
, - . Keycloak.
EventListenerProvider
EventListenerProvider
:
@Slf4j
@NoArgsConstructor
public class CustomEventListenerProvider implements EventListenerProvider {
@Override
public void onEvent(Event event) {
log.info("Caught event {}", EventUtils.toString(event));
}
@Override
public void onEvent(AdminEvent adminEvent, boolean b) {
log.info("Caught admin event {}", EventUtils.toString(adminEvent));
}
@Override
public void close() {
}
}
:
onEvent
, , , , . : , id , IP . .
onAdminEvent
"" , : Keycloak.
close
, .
EventUtils
, .
EventListenerProviderFactory
EventListenerProviderFactory
:
public class CustomEventListenerProviderFactory implements EventListenerProviderFactory {
private static final String LISTENER_ID = "event-listener-extension";
@Override
public EventListenerProvider create(KeycloakSession session) {
return new CustomEventListenerProvider();
}
@Override
public void init(Config.Scope scope) {
}
@Override
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return LISTENER_ID;
}
}
, , :
create
CustomEventListenerProvider
. .CustomEventListenerProviderFactory
Keycloak.
init
.
postInit
.
close
Keycloak .
getId
.
. , , , Event
AdminEvent
:
@UtilityClass
public class EventUtils {
public static 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();
}
public static 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();
}
}
CustomEventListenerProviderFactory
Keycloak.
org.keycloak.events.EventListenerProviderFactory
src/main/resources/META-INF/services/
. .
:
ru.event.listener.extension.factory.CustomEventListenerProviderFactory
. Keycloak . .
JAR . Maven, , target
JAR . , -sources
. keycloak-logging-plugin.jar
. :
mvn clean package
JAR keycloak-logging-plugin.jar
Keycloak <_KEYCLOAK>/standalone/deployments/
, Keycloak . , keycloak hot swap " ". JAR , keycloak .
, .
, Keycloak :
19:37:58,203 INFO [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0027: Starting deployment of "event-listener-keycloak-extension.jar" (runtime-name: "event-listener-keycloak-extension.jar")
19:37:58,322 INFO [org.keycloak.subsystem.server.extension.KeycloakProviderDeploymentProcessor] (MSC service thread 1-7) Deploying Keycloak provider: event-listener-keycloak-extension.jar
19:37:58,334 WARN [org.keycloak.services] (MSC service thread 1-7) KC-SERVICES0047: event-listener-extension (ru.event.listener.extension.factory.CustomEventListenerProviderFactory) is implementing the internal SPI eventsListener. This SPI is internal and may change without notice
19:37:58,366 INFO [org.jboss.as.server] (DeploymentScanner-threads - 1) WFLYSRV0010: Deployed "event-listener-keycloak-extension.jar" (runtime-name : "event-listener-keycloak-extension.jar")
JAR .deployed
.
. Keycloak. Events → Config:
Save.
- . :
20:02:14,474 INFO [ru.event.listener.extension.CustomEventListenerProvider] (default task-11) Caught event type=LOGIN, realmId=master, clientId=account-console, userId=8cbc9aec-0c5f-45e0-b614-baf9e96c2278, ipAddress=127.0.0.1, auth_method=openid-connect, auth_type=code, redirect_uri=http://localhost:8080/auth/realms/master/account/#/, consent=no_consent_required, code_id=007a3edc-4541-4648-b1e6-44c30349c001, username=test
:
20:03:13,143 INFO [ru.event.listener.extension.CustomEventListenerProvider] (default task-11) Caught event type=LOGOUT, realmId=master, clientId=null, userId=8cbc9aec-0c5f-45e0-b614-baf9e96c2278, ipAddress=127.0.0.1, redirect_uri=http://localhost:8080/auth/realms/master/account/#/
:
20:03:42,204 WARN [org.keycloak.events] (default task-11) type=LOGIN_ERROR, realmId=master, clientId=account-console, userId=8cbc9aec-0c5f-45e0-b614-baf9e96c2278, ipAddress=127.0.0.1, error=invalid_user_credentials, auth_method=openid-connect, auth_type=code, redirect_uri=http://localhost:8080/auth/realms/master/account/#/, code_id=f0d48657-3673-4875-bb72-a7f1d89b6d31, username=test, authSessionParentId=f0d48657-3673-4875-bb72-a7f1d89b6d31, authSessionTabId=h6V1w1C3Zjk
(AdminEvent
):
20:05:05,045 INFO [ru.event.listener.extension.CustomEventListenerProvider] (default task-20) Caught admin event operationType=ACTION, realmId=master, clientId=cff15a39-3a5d-49c6-baf1-1c8d9dee1ce6, userId=a64026c4-689f-4213-8229-b8ac471150ea, ipAddress=127.0.0.1, resourcePath=users/8cbc9aec-0c5f-45e0-b614-baf9e96c2278/reset-password
P.S.
, Keycloak , , (Keycloak , brute force detection).
, , - , , Keycloak . , Keycloak, . , - , BruteForceProtector
, , .
, , , , .