Skip to content

Commit

Permalink
Add example for publishing events via NATS
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasdarimont committed May 4, 2024
1 parent 2c55f26 commit 16bb873
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 25 deletions.
4 changes: 3 additions & 1 deletion deployments/local/dev/docker-compose-keycloakx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ services:
- ./run/keycloakx/logs:/opt/keycloak/logs:z
- ./run/keycloakx/perf:/opt/keycloak/perf:z
# Add keycloak extensions
- ../../../keycloak/extensions/target/extensions.jar:/opt/keycloak/providers/extensions.jar:z
# - ../../../keycloak/extensions/target/extensions.jar:/opt/keycloak/providers/extensions.jar:z
- ../../../keycloak/extensions/target/extensions-jar-with-dependencies.jar:/opt/keycloak/providers/extensions.jar:z

# Add third-party extensions
# - ./keycloak-ext/keycloak-metrics-spi-3.0.0.jar:/opt/keycloak/providers/keycloak-metrics-spi.jar:z
- ./keycloak-ext/keycloak-restrict-client-auth-23.0.0.jar:/opt/keycloak/providers/keycloak-restrict-client-auth.jar:z
Expand Down
4 changes: 3 additions & 1 deletion deployments/local/dev/docker-compose-nats.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ services:
ports:
- "8222:8222"
- "4222:4222"
hostname: acme-nats
command: -c /etc/my-server.conf --name acme-nats -p 4222
volumes:
- ./nats/server.conf:/etc/my-server.conf
26 changes: 26 additions & 0 deletions deployments/local/dev/nats/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
NATS Support
---

```
nats context add localhost --description "Localhost"
```

Add username / password in context config
```
vi ~/.config/nats/context/localhost.json
```

List contexts
```
nats context ls
```

Select context
```
nats ctx select localhost
```

Nats subscribe to keycloak subject
```
nats sub acme.iam.keycloak>
```
23 changes: 23 additions & 0 deletions deployments/local/dev/nats/server.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
accounts: {
$SYS: {
users: [
{ user: "admin", password: "password" }
]
},
KEYCLOAK: {
jetstream: enabled,
users: [
{ user: "keycloak", password: "keycloak" }
]
}
}

jetstream {}

#cluster: {
# name: LOCAL,
# port: 6222,
# routes: [
# "nats://acme_nats_1:6222"
# ]
#}
36 changes: 28 additions & 8 deletions keycloak/extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
<groupId>io.smallrye</groupId>
<artifactId>smallrye-health</artifactId>
<version>${smallrye-health.version}</version>
<!-- <scope>provided</scope>-->
<scope>provided</scope>
</dependency>

<dependency>
Expand All @@ -122,7 +122,7 @@
<groupId>org.eclipse.microprofile.health</groupId>
<artifactId>microprofile-health-api</artifactId>
<version>${microprofile-health-api.version}</version>
<!-- <scope>provided</scope>-->
<scope>provided</scope>
</dependency>

<dependency>
Expand All @@ -132,6 +132,13 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.nats</groupId>
<artifactId>jnats</artifactId>
<version>${jnats.version}</version>
<!-- needs to be explicitly included! -->
</dependency>

<!-- Test Dependencies -->
<dependency>
<groupId>org.keycloak</groupId>
Expand All @@ -151,11 +158,6 @@
</exclusions>
</dependency>

<dependency>
<groupId>io.nats</groupId>
<artifactId>jnats</artifactId>
<version>${jnats.version}</version>
</dependency>

<dependency>
<groupId>org.jboss.resteasy</groupId>
Expand Down Expand Up @@ -293,7 +295,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<manifest>
Expand All @@ -302,6 +304,24 @@
</archive>
</configuration>
</plugin>

<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,55 @@

import com.google.auto.service.AutoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.jbosslog.JBossLog;
import org.keycloak.Config;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ServerInfoAwareProviderFactory;

import java.util.Map;

@JBossLog
@RequiredArgsConstructor
public class AcmeEventPublisherEventListener implements EventListenerProvider {

public static final String ID = "acme-event-publisher";

private final KeycloakSession session;

private final EventPublisher publisher;

@Override
public void onEvent(Event event) {
// NOOP
publisher.publish("acme.iam.keycloak.user", enrichUserEvent(event));
}

private Object enrichUserEvent(Event event) {
return event;
}

@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
publisher.publish("acme.iam.keycloak.admin", enrichAdminEvent(event, includeRepresentation));
}

private Object enrichAdminEvent(AdminEvent event, boolean includeRepresentation) {
return event;
}

@Override
public void close() {

// NOOP
}

@AutoService(EventListenerProviderFactory.class)
public static class Factory implements EventListenerProviderFactory {
public static class Factory implements EventListenerProviderFactory, ServerInfoAwareProviderFactory {

private EventPublisher publisher;

@Override
public String getId() {
Expand All @@ -42,18 +59,42 @@ public String getId() {

@Override // return singleton instance, create new AcmeAuditListener(session) or use lazy initialization
public EventListenerProvider create(KeycloakSession session) {
return new AcmeEventPublisherEventListener(session);
return new AcmeEventPublisherEventListener(session, publisher);
}

@Override
public void init(Config.Scope config) {
/* configure factory */
publisher = createNatsPublisher(config);
}

private NatsEventPublisher createNatsPublisher(Config.Scope config) {

String url = config.get("nats-url", "nats://acme-nats:4222");
String username = config.get("nats-username", "keycloak");
String password = config.get("nats-password", "keycloak");

var nats = new NatsEventPublisher(url, username, password);
nats.init();

log.info("Created new NatsPublisher");

return nats;
}

@Override // we could init our provider with information from other providers
public void postInit(KeycloakSessionFactory factory) { /* post-process factory */ }

@Override // close resources if necessary
public void close() { /* release resources if necessary */ }
public void close() {
if (publisher != null) {
publisher.close();
}
}

@Override
public Map<String, String> getOperationalInfo() {
return publisher.getOperationalInfo();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.thomasdarimont.keycloak.custom.eventpublishing;

import java.util.Map;

public interface EventPublisher {

void publish(String topic, Object event);

Map<String, String> getOperationalInfo();

void init();

void close();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,67 @@
import io.nats.client.Connection;
import io.nats.client.Nats;
import io.nats.client.Options;
import lombok.RequiredArgsConstructor;
import lombok.extern.jbosslog.JBossLog;
import org.keycloak.util.JsonSerialization;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class NatsEventPublisher {
@JBossLog
@RequiredArgsConstructor
public class NatsEventPublisher implements EventPublisher {

public void publish(Object event) {
private final String url;

String natsURL = "nats://acme-nats:4222";
private final String username;

private final String password;

private Connection connection;

public void publish(String subject, Object event) {
try {
byte[] messageBytes = JsonSerialization.writeValueAsBytes(event);
connection.publish(subject, messageBytes);
} catch (IOException e) {
log.warn("Could not serialize event", e);
}
}

public Map<String, String> getOperationalInfo() {
return Map.of("url", url, "nats-username", username, "status", getStatus());
}

public String getStatus() {
if (connection == null) {
return null;
}
return connection.getStatus().name();
}

public void init() {

Options options = Options.builder() //
.connectionName("keycloak") //
.server(natsURL) //
.userInfo(username, password) //
.server(url) //
.build();

try (Connection nc = Nats.connect(options)) {
byte[] messageBytes = JsonSerialization.writeValueAsBytes(event);
nc.publish("acme.iam.keycloak.admin", messageBytes);
} catch (InterruptedException | IOException e) {
e.printStackTrace();
try {
connection = Nats.connect(options);
} catch (Exception e) {
log.warn("Could not connect to nats server", e);
}
}

public void close() {
if (connection != null) {
try {
connection.close();
} catch (InterruptedException e) {
log.warn("Could not close connection", e);
}
}
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<docker-maven-plugin.version>0.43.4</docker-maven-plugin.version>
<maven-failsafe-plugin.version>3.2.5</maven-failsafe-plugin.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.4.0</maven-jar-plugin.version>
</properties>

<build>
Expand Down

0 comments on commit 16bb873

Please sign in to comment.