为KeyCloak构建事件监听器SPI(插件)

在此博客中,我将讨论如何为KeyCloak构建事件侦听器插件(称为SPI)。

那么,什么是Keycloak

KeycloakRedHat构建的开源身份和访问管理框架。 它提供了许多高级功能,例如SSO,社交身份验证,对多种身份验证协议的支持等。在此处了解更多信息: https : //www.keycloak.org/

但是最重​​要的功能之一是能够通过简单地构建插件来扩展KeyCloak的任何功能。

在今年夏天的实习期间,我们需要记录KeyCloak中发生的所有用户(和管理员)事件,并将它们发送到外部系统进行分析。 在许多情况下都需要这样做。 (一个示例是,如果您使用外部SIEM记录和分析事件)

默认情况下,KeyCloak日志不包含用户/管理员事件。 即使启用了该功能,也很难构建一个外部系统来监视和解析日志以提取所需的事件。 相反,我们可以为KeyCloak构建一个插件以挂接到系统中,并在事件发生时执行“某些操作” (在我们的情况下,触发外部API调用)

因此,让我们建立一个:)

注意 :这里提供了事件侦听器的完整代码。

GitHub徽标 adwait-thattey / keycloak-event-listener-spi

示例事件侦听器SPI,用于keycloak

我将在这里使用Maven来管理依赖项和构建项目。

因此,让我们首先整理一下pom.xml 。

(如果您不熟悉Maven,我们将在Maven中使用pom.xml文件列出所有项目详细信息,包括所有依赖项)

(如果看不到上述要点,则可以在此处找到该文件

pom.xml ,我们定义了父级详细信息,项目名称Sample Event Listener ,版本,工件ID(此处为sample_event_listener ),依赖项和构建配置。


下一步是实施SPI。 为此,我们需要实现2个类。 ProviderProviderFactory

因此,让我们在src/main/java创建我们的包。
此处的包名称为com.coderdude.sampleeventlistenerprovider.provider

coderdude :因为我的开发别名是coderdude:D

sampleeventlistenerprovider :可以更短一些,但让我们保留

provider :最后一个提供程序在那里,因为您的提供程序中可能会使用其他模块。

现在,此软件包将包含上面讨论的2个类。
Provider类包含插件的实际逻辑。 ProviderFactory是用于初始化ProviderFactory的包装器。 区别很重要

  • 仅在启动KeyCloak时初始化Factory 每次需要时, Factory都会创建一个Provider的新实例。 (就我们而言,每次事件发生)
  • 仅存在1个Factory实例。 多个提供程序可以同时存在(例如2个事件同时发生)。
  • 提供程序完成任务后将被销毁。 只要KeyCloak运行,工厂就存在。
  • Factory中的任何错误都将使KeyCloak崩溃。 Provider中的错误只会进入日志,而Keycloak的其余部分将正常运行

因此,让我们从创建提供者开始。
的类的名称将是SampleEventListenerProvider它实现了EventListenerProvider接口(该接口由KeyCloak提供)

package com . coderdude . sampleeventlistenerprovider . provider ;

import org.keycloak.events.Event ;
import org.keycloak.events.EventListenerProvider ;
import org.keycloak.events.admin.AdminEvent ;

import java.util.Map ;


public class SampleEventListenerProvider implements EventListenerProvider {

    public SampleEventListenerProvider () {
    }


}

暂时保留这些进口。 我们将需要它们。
因此,在这里,我们将把所有事件打印到控制台。 所有事件均由2个类提供: org.keycloak.events.Eventorg.keycloak.events.admin.AdminEvent
每当普通用户执行某项操作时,就会发生普通事件。 管理员执行某事时会发生管理事件。

我们需要编写适当的方法将这些类对象转换为可读的字符串。
这是为Event构建字符串的方法

我们正在捕获所有参数,错误和详细信息。 (因此映射,因为细节是一个数组)

private 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 ();

    }

当然,这是一个非常幼稚的实现。 我们实际上所做的是定义将这些事件包装在其他对象中并向外部系统进行API调用的方法。 但这现在将起作用。
我们可以为AdminEvent构建类似的方法。 您将在主要的完整代码中找到它。

完成此操作后,我们需要覆盖由
EventListenerProvider接口。 这些是onEventclose

这里是

    @Override
    public void onEvent ( Event event ) {

        System . out . println ( "Event Occurred:" + toString ( event ));
    }

    @Override
    public void onEvent ( AdminEvent adminEvent , boolean b ) {

        System . out . println ( "Admin Event Occurred:" + toString ( adminEvent ));
    }

    @Override
    public void close () {

    }

onEvent是事件发生时实际调用的方法。 我们需要两次重载onEvent来捕获EventAdminEvent
最后,在销毁类之前立即调用close方法。 有点像一个破坏者。 即使我们不需要使用它,也需要重写它。

您可以在此处找到完整的类代码(以及AdminEvent的字符串实现)


下一步是实现ProviderFactory
类的名称是SampleEventListenerProviderFactory它实现EventListenerProviderFactory

这是代码:

(如果看不到上述要点,则可以在此处找到该文件

我们在这里覆盖了多种方法。 主要的是creategetId create方法应该初始化并返回提供者的实例(在我们的示例中为SampleEventListenerProvider )。 getId应该返回一个带有插件名称的字符串


下一个也是最后一个任务是提供指向我们班级的链接。 为此,我们需要创建资源。
src/main创建一个名为resources的文件夹(与java文件夹一起)
现在,在resources/META-INF/services/创建以下文件,名为org.keycloak.events.EventListenerProviderFactory 请注意,文件位置的完整路径是src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory

该文件仅包含一行内容,其中包含我们工厂类的软件包和名称

com . coderdude . sampleeventlistenerprovider . provider . SampleEventListenerProviderFactory

而已。 我们已经编写了插件。 现在,我们来构建和打包它。
我用过Maven来构建和打包

打包完成后,您应该在目标目录中看到jarsources
这是我的最终目录结构
为KeyCloak构建事件监听器SPI(插件)_第1张图片

我们只需要sample-event-listener.jar

-
现在是时候将插件部署到KeyCloak了。

让我们首先使用KeyCloak进行设置。 您可以在https://www.keycloak.org/docs/latest/getting_started/index.html上找到入门指南。

快速下载并创建管理员用户,然后登录KeyCloak。
现在,我们创建一个名为newrealm的新领域,并在该新领域中添加一个名为newuser001的用户。

我们还为该新用户创建一个密码

是时候部署我们很棒的插件了

部署过程非常简单。 我们需要将sample-event-listener.jar复制到$KEYCLOAK_DIR/standalone/deployments/ ,其中$ KEYCLOAK_DIR是主KeyCloak目录(解压缩后)

KeyCloak支持热重装。 因此,一旦我们复制了jar文件,keycloak应该重新加载并部署插件。 但是请确保我们重新启动Keycloak服务器。

您应该会看到这样的一行

Deployed "sample-event-listener.jar" (runtime-name : "sample-event-listener.jar")

现在,我们需要允许该插件监听事件。
转到newrealm->manage->events->config或此URL /auth/admin/master/console/#/realms/newrealm/events-settings 确保将newrealm替换为您创建的领域的名称

在事件监听器配置中,将sample_event_listener添加到列表中,然后点击保存。

现在,我们的插件应该能够捕获所有事件。


让我们测试一下

使用上面创建的用户登录到新领域。

您应该看到控制台中发生了一个事件

17:03:01,797 INFO  [stdout] (default task-5) Event Occurred:type=LOGIN, realmId=newrealm, clientId=account, userId=efc09972-6166-4ed6-9ca0-15c030e47f54, ipAddress=127.0.0.1, auth_method=openid-connect, auth_type=code, redirect_uri=http://localhost:8180/auth/realms/newrealm/account/login-redirect, consent=no_consent_required, code_id=78db58ed-3c99-4d42-aced-b69873c59f12, username=newuser001

注销也应被捕获

17:03:51,211 INFO  [stdout] (default task-5) Event Occurred:type=LOGOUT, realmId=newrealm, clientId=null, userId=efc09972-6166-4ed6-9ca0-15c030e47f54, ipAddress=127.0.0.1, redirect_uri=http://localhost:8180/auth/realms/newrealm/account/

还会尝试使用错误的密码登录(因为我们也在捕获错误)

17:04:04,505 WARN  [org.keycloak.events] (default task-5) type=LOGIN_ERROR, realmId=master, clientId=security-admin-console, userId=null, ipAddress=127.0.0.1, error=user_not_found, auth_method=openid-connect, auth_type=code, redirect_uri=http://localhost:8180/auth/admin/master/console/#/realms/newrealm/users, code_id=59a85ee0-a8f6-4fad-8667-f72de2da18fd, username=newuser001

在我的控制台中看起来像这样

瞧! 我们的插件能够捕获事件


包起来:

再一次,完整的代码在这里可用:

GitHub徽标 adwait-thattey / keycloak-event-listener-spi

示例事件侦听器SPI,用于keycloak

这是一个非常基本的例子。 我们可以做的更多。 keycloak事件提供了很多有用的信息,这些信息可以捕获。 像当前领域一样,尝试登录的人的IP地址,如果是api登录,则访问令牌ID,等等。


如果您喜欢此博客,请点击:)

再见!

等待Thattey,

https://adwait-thattey.github.io/

From: https://dev.to/adwaitthattey/building-an-event-listener-spi-plugin-for-keycloak-2044

你可能感兴趣的:(为KeyCloak构建事件监听器SPI(插件))