Continúo con el tema de mi colega sobre Keycloak.
Quién no necesita agua, sino solo un código de muestra, salte aquí .
Keycloak se utiliza con bastante frecuencia como una solución de gestión de identidad y acceso para aplicaciones modernas dentro de aplicaciones empresariales.
Keycloak está escrito en el lenguaje Java, y los creadores inicialmente presentaron una oportunidad muy conveniente para expandir la funcionalidad de la solución lista para usar con los llamados complementos u oficialmente: extensiones .
Una extensión es un proyecto Java normal que consta de clases que amplían las clases / interfaces de Keycloak predeterminadas con funcionalidad adicional requerida. Además, puede expandir la funcionalidad de casi cualquier clase de Keycloak y para cualquier propósito: desde un cambio mínimo en el texto de un mensaje sobre un usuario incorrecto que ingresó una contraseña, hasta vincular un Discord , como proveedor de identidad .
Este artículo se centrará en ampliar el detector de eventos predeterminado en 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
, , .
, , , , .