DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • MongoDB Change Streams and Go
  • How to Design Event Streams, Part 3
  • How to Design Event Streams, Part 2
  • How to Design Event Streams, Part 1

Trending

  • Slopsquatting: Building a Scanner That Catches AI-Hallucinated Packages Before They Reach Production
  • Is the Data Warehouse Dead? 3 Patterns From Enterprise Architecture That Answer This Question
  • Stop Debugging Glue Jobs Manually: Building an Agentic Observability Layer for Data Pipelines
  • Observability for Agents and Workflows: Tracing Prompts, Tool Calls, and Business Outcomes End-to-End
  1. DZone
  2. Coding
  3. JavaScript
  4. Integrating Red Hat Single Sign-On With AMQ Streams for Auditing Events

Integrating Red Hat Single Sign-On With AMQ Streams for Auditing Events

Do you need to do audit events of your SSO Instance? Check this easy way to do it with AMQ Streams.

By 
Raffael Mendes user avatar
Raffael Mendes
·
May. 01, 21 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
10.5K Views

Join the DZone community and get the full member experience.

Join For Free

Here I am again with another take from the field.

Red Hat Single Sign-on (RH-SSO) is the enterprise-ready version of Keycloak, and one thing that is most commonly asked, especially for big customers is, "How do we audit all the events?"

The out of the box way to do this is to save on the database, but this poses a silent and somewhat problematic threat: performance. RH-SSO generates a ton of events and, when doing complex authorization flows with lots of steps, this can become a burden on performance.

And then enters AMQ Streams, Red Hat's enterprise-ready version of Apache Kafka, with a simple Service Provider Interface (SPI) that we're going to build below.

All the code written below is available on GitHub.

Let's Start

So first, we'll Download RH-SSO the ZIP distribution and unzip and start it

Shell
 




xxxxxxxxxx
1


 
1
unzip rh-sso-7.4.0.zip -d /opt/
2
/opt/rh-sso-7.4/bin/standalone.sh 


After deployment, we check http://localhost:8080/auth to check if started correctly.

Now, with RH-SSO started and working we're going to build our custom SPI

Let's Code

First, we create a simple Java project with these dependencies

XML
 




x


 
1
        <dependency>
2
            <groupId>org.keycloak</groupId>
3
            <artifactId>keycloak-core</artifactId>
4
            <version>${keycloak.version}</version>
5
        </dependency>
6
        <dependency>
7
            <groupId>org.keycloak</groupId>
8
            <artifactId>keycloak-server-spi</artifactId>
9
            <version>${keycloak.version}</version>
10
        </dependency>
11
         <dependency>
12
            <groupId>org.keycloak</groupId>
13
            <artifactId>keycloak-server-spi-private</artifactId>
14
            <scope>provided</scope>
15
            <version>${keycloak.version}</version>
16
        </dependency>
17
        <dependency>
18
            <groupId>org.keycloak</groupId>
19
            <artifactId>keycloak-services</artifactId>
20
            <scope>provided</scope>
21
            <version>${keycloak.version}</version>
22
        </dependency>
23
        <dependency>
24
            <groupId>org.apache.kafka</groupId>
25
            <artifactId>kafka-clients</artifactId>
26
            <version>2.7.0</version>
27
        </dependency>


And to build our custom SPI, we start by creating a class that implements the EventListener Provider

Java
 




xxxxxxxxxx
1
28


 
1
package com.rmendes.provider;
2

          
3
import org.keycloak.events.Event;
4
import org.keycloak.events.EventListenerProvider;
5
import org.keycloak.events.admin.AdminEvent;
6

          
7
public class KafkaEventListener implements EventListenerProvider{
8

          
9
    @Override
10
    public void close() {
11
        // TODO Auto-generated method stub
12
        
13
    }
14

          
15
    @Override
16
    public void onEvent(Event arg0) {
17
        // TODO Auto-generated method stub
18
        
19
    }
20

          
21
    @Override
22
    public void onEvent(AdminEvent arg0, boolean arg1) {
23
        // TODO Auto-generated method stub
24
        
25
    }
26

          
27
}
28

          


We can see that this is a very straightforward interface. We have two methods, one to handle login events and one to handle admin events.

Now we start configuring our AMQ Streams client

Java
 




xxxxxxxxxx
1
11


 
1
private static final String KAFKA_BOOTSTRAP_SERVERS = "localhost:9092";
2
private static final String KAFKA_TOPIC_EVENTS = "rhsso.events";
3
private static final String KAFKA_TOPIC_ADMIN_EVENTS = "rhsso.admin.events";
4

          
5
private Properties kafkaProperties() {
6
        Properties p = new Properties();
7
        p.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_BOOTSTRAP_SERVERS);
8
        p.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
9
        p.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
10
        return p;
11
    }


We configured our bootstrap server, and out topics to send our records.

Now we create a simple send method 

Java
 




xxxxxxxxxx
1


 
1
private void sendRecordKafka(String topic, String value) {
2
        Thread.currentThread().setContextClassLoader(null);
3
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(kafkaProperties());
4
        ProducerRecord<String, String> record = new ProducerRecord<String, String>(topic, value);
5
        producer.send(record);
6
        producer.flush();
7
        producer.close();
8
    }


And we can just call the send method

Java
 




xxxxxxxxxx
1


 
1
    public void onEvent(Event event) {
2
        sendRecordKafka(KAFKA_TOPIC_EVENTS, eventStringfier(event));
3
    }
4

          
5
    public void onEvent(AdminEvent event, boolean bool) {
6
        sendRecordKafka(KAFKA_TOPIC_ADMIN_EVENTS, adminEventStringfier(event));
7
        
8
    }


The stringifier methods are just to create a string representation of the events.

Creating the Provider Factory

When deploying a custom SPI, we need to create a provider factory that is going to instantiate our EventListener, and we can also handle some configurations on creation and destruction.

To achieve this we implement the interface EventListenerProviderFactory

Java
 




xxxxxxxxxx
1
39


 
1
package com.rmendes.provider;
2

          
3
import org.keycloak.Config.Scope;
4
import org.keycloak.events.EventListenerProvider;
5
import org.keycloak.events.EventListenerProviderFactory;
6
import org.keycloak.models.KeycloakSession;
7
import org.keycloak.models.KeycloakSessionFactory;
8

          
9
import com.rmendes.provider.KafkaEventListener;
10

          
11
public class KafkaCustomProviderFactory implements EventListenerProviderFactory{
12

          
13
    public void close() {
14
        // TODO Auto-generated method stub
15
        
16
    }
17

          
18
    public EventListenerProvider create(KeycloakSession arg0) { 
19

          
20
        return new KafkaEventListener();
21
    }
22

          
23
    public String getId() {
24
        return "kafka-custom-listener";
25
    }
26

          
27
    public void init(Scope arg0) {
28
        // TODO Auto-generated method stub
29
        
30
    }
31

          
32
    public void postInit(KeycloakSessionFactory arg0) {
33
        // TODO Auto-generated method stub
34
        
35
    }
36
}
37

          


And the last step to make Keycloak see this and be able to show it on the web interface is creating a file on src/main/resources/META-INF/ named org.keycloak.events.EventListenerProviderFactory with the full qualified name of our ProviderFactory class 

Plain Text
 




xxxxxxxxxx
1


 
1
com.rmendes.provider.KafkaCustomProviderFactory



Now we can finally deploy our application, and we're going to take advantage of RH-SSO automatic  deployment by simply copying our SPI to the deployments folder

Shell
 




xxxxxxxxxx
1


 
1
mvn clean package 
2
cp target/kafka-keycloak-spi.jar /opt/rh-sso-7.4/standalone/deployments/



Starting AMQ-Streams

In a terminal window, extract the amq-streams ZIP and start Zookeeper

Shell
 




xxxxxxxxxx
1


 
1
unzip Downloads/amq-streams-1.7.0-bin.zip -d /opt/ 
2

          
3
/opt/kafka_2.12-2.7.0.redhat-00005/bin/zookeeper-server-start.sh /opt/kafka_2.12-2.7.0.redhat-00005/config/zookeeper.properties



Now start the Kafka broker

Shell
 




xxxxxxxxxx
1


 
1
/opt/kafka_2.12-2.7.0.redhat-00005/bin/kafka-server-start.sh /opt/kafka_2.12-2.7.0.redhat-00005/config/server.properties


Configuring RH-SSO

Now we just need to tell RH-SSO to send events to our SPI 

In the events session, if everything is working, we should see our custom listener in the select box

And then we enable both the Login events and Admin events settings

We save these configs and now we go to AMQ-Streams to see the magic working. Try some logins and logouts with various users and, to check that the events are being generated, we can use the command below to consume the messages.

Shell
 




xxxxxxxxxx
1


 
1
/opt/kafka_2.12-2.7.0.redhat-00005/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic rhsso.events --from-beginning
2
clientID: null Type: LOGOUT UserID: b141c01a-30e6-4a1a-a03d-50f45b518fd6
3
clientID: security-admin-console Type: LOGIN UserID: b141c01a-30e6-4a1a-a03d-50f45b518fd6
4
clientID: security-admin-console Type: CODE_TO_TOKEN UserID: b141c01a-30e6-4a1a-a03d-50f45b518fd6
5

          



And that's it, our events are in a reliable tool that can be used to do many things, maybe real-time intrusion detection or plain, simple auditing. Feel free to explore.

Thanks!

Event Service provider interface Stream (computing)

Opinions expressed by DZone contributors are their own.

Related

  • MongoDB Change Streams and Go
  • How to Design Event Streams, Part 3
  • How to Design Event Streams, Part 2
  • How to Design Event Streams, Part 1

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook