Build
The deployment process is pretty straightforward. We need to copy the sample-event-listener.jar to $KEYCLOAK_DIR/standalone/deployments/ where $KEYCLOAK_DIR is the main KeyCloak directory.
- The
$KEYCLOAK_DIRused to be/opt/jboss/keycloak/
Build .jar as output in /target:
$ mvn clean installTesting with docker:
- Copy the
.jarto/dockerfolder.
$ cd docker && docker build .
$ sudo docker run -p 8080 --network="host" <image_id>FROM quay.io/keycloak/keycloak:12.0.4
COPY ./kafka-user-register-event-listener.jar /opt/jboss/keycloak/standalone/deployments/kafka-user-register-event-listener.jar
ENV KEYCLOAK_USER=admin
ENV KEYCLOAK_PASSWORD=admin
CMD ["-b", "0.0.0.0", "-c", "standalone.xml"]KeycloakCustomEventListener.java
package plugin;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
public class KeycloakCustomEventListener implements EventListenerProvider {
@Override
public void onEvent(Event event) {
if (event.getType() == EventType.REGISTER) {
System.out.println("[+] NEW USER REGISTER: " + event.getUserId());
Producer.publishEvent(Producer.KAFKA_TOPIC,"{" + event.getType().toString() + ":" + event.getUserId() + "}");
}
}
@Override
public void onEvent(AdminEvent adminEvent, boolean b) {
System.out.println("[+] ADMIN EVENT: "+adminEvent.getResourceType().name());
Producer.publishEvent(adminEvent.getOperationType().toString(), adminEvent.getAuthDetails().getUserId());
}
@Override
public void close() {
}
}KeycloakCustomEventListenerProviderFactory.java
package plugin;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
public class KeycloakCustomEventListenerProviderFactory implements EventListenerProviderFactory {
@Override
public EventListenerProvider create(KeycloakSession keycloakSession) {
return new KeycloakCustomEventListener();
}
@Override
public void init(Config.Scope scope) {
}
@Override
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
}
@Override
public void close() {
}
// Defines the name for keycloak GUI selection
@Override
public String getId() {
return "kafka-user-register-event-listener";
}
}Producer.java
package plugin;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class Producer {
final static String KAFKA_BOOTSTRAP_SERVER = "kafka-cp-kafka.kafka.svc.cluster.local:9092";
//final static String KAFKA_BOOTSTRAP_SERVER = "localhost:9092";
final static String KAFKA_TOPIC = "keycloak-user-register";
public static void publishEvent(String topic, String value){
//reset thread context
resetThreadContext();
// create the producer
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(getProperties());
// create a producer record
ProducerRecord<String, String> eventRecord = new ProducerRecord<String, String>(topic, value);
// send data - asynchronous
producer.send(eventRecord);
// flush data
producer.flush();
// flush and close producer
producer.close();
}
private static void resetThreadContext() {
Thread.currentThread().setContextClassLoader(null);
}
public static Properties getProperties() {
Properties properties = new Properties();
properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_BOOTSTRAP_SERVER);
properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
return properties;
}
}