You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何订阅Keycloak事件?请求提供Java处理user_temporarily_disabled示例

Keycloak实时接收user_temporarily_disabled事件的Java实现方案

一、可行性说明

完全可行,Keycloak原生支持实时事件捕获机制,无需定时轮询。当用户因连续输错密码被临时禁用时,会触发USER_TEMPORARILY_DISABLED事件,我们可以通过自定义事件监听器或Webhook两种方式实时接收并处理。

二、核心实现方案:自定义Keycloak事件监听器

通过开发自定义事件监听器Jar包部署到Keycloak,事件触发时直接调用本地业务服务接口,是最直接的Java实现方式。

1. Maven依赖配置

注意版本需与你的Keycloak版本匹配,示例使用22.0.5版本:

<dependencies>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-core</artifactId>
        <version>22.0.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-server-spi</artifactId>
        <version>22.0.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-server-spi-private</artifactId>
        <version>22.0.5</version>
        <scope>provided</scope>
    </dependency>
    <!-- 调用外部服务所需的HTTP客户端 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.1.2</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

2. 自定义事件监听器代码

事件处理核心类

import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import org.springframework.web.client.RestTemplate;

public class UserDisabledEventListener implements EventListenerProvider {

    private final KeycloakSession session;
    private final RestTemplate restTemplate;
    // 替换为你的本地业务服务地址
    private static final String BUSINESS_SERVICE_URL = "http://your-local-service/api/user/disabled-handler";

    public UserDisabledEventListener(KeycloakSession session) {
        this.session = session;
        this.restTemplate = new RestTemplate();
    }

    @Override
    public void onEvent(Event event) {
        // 仅处理用户临时禁用事件
        if (EventType.USER_TEMPORARILY_DISABLED.equals(event.getType())) {
            String userId = event.getUserId();
            String username = session.users().getUserById(session.getContext().getRealm(), userId).getUsername();
            
            // 构造业务请求参数
            UserDisabledRequest request = new UserDisabledRequest();
            request.setUserId(userId);
            request.setUsername(username);
            request.setDisableReason("连续10次输入错误密码");
            
            // 调用本地业务服务
            try {
                restTemplate.postForObject(BUSINESS_SERVICE_URL, request, Void.class);
            } catch (Exception e) {
                // 异常日志记录
                session.getContext().getLogger().error("处理用户禁用事件时调用业务服务失败", e);
            }
        }
    }

    @Override
    public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
        // 无需处理管理端事件可留空
    }

    @Override
    public void close() {
        // 资源释放操作
    }

    // 请求参数内部类
    private static class UserDisabledRequest {
        private String userId;
        private String username;
        private String disableReason;

        // Getter & Setter
        public String getUserId() { return userId; }
        public void setUserId(String userId) { this.userId = userId; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getDisableReason() { return disableReason; }
        public void setDisableReason(String disableReason) { this.disableReason = disableReason; }
    }
}

监听器工厂类

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 UserDisabledEventListenerFactory implements EventListenerProviderFactory {

    @Override
    public EventListenerProvider create(KeycloakSession session) {
        return new UserDisabledEventListener(session);
    }

    @Override
    public void init(Config.Scope config) {
        // 可从Keycloak配置文件读取自定义参数,如业务服务地址
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {
        // 后续初始化操作
    }

    @Override
    public void close() {
        // 资源释放
    }

    @Override
    public String getId() {
        // 监听器唯一标识,控制台配置时需使用此值
        return "user-disabled-event-listener";
    }
}

3. 打包与部署

  1. 在src/main/resources/META-INF/services目录下创建文件org.keycloak.events.EventListenerProviderFactory,内容为工厂类的全限定名:
    com.yourcompany.keycloak.listener.UserDisabledEventListenerFactory
    
  2. 用Maven打包成Jar包,放入Keycloak的providers目录
  3. 重启Keycloak,在对应Realm的Events > Event Listeners中添加user-disabled-event-listener

三、备选方案:Webhook事件订阅(无需自定义Jar)

若不想开发自定义监听器,可直接通过Keycloak控制台配置Webhook:

  • 进入对应Realm的Events > Settings,开启事件记录并勾选USER_TEMPORARILY_DISABLED
  • 进入Events > Webhooks,添加Webhook,设置本地服务接口地址,选择触发事件为USER_TEMPORARILY_DISABLED
  • 注意:需确保Keycloak能访问到你的本地服务(本地开发可使用内网穿透工具)

四、本地业务服务接收示例

以下是Spring Boot接口示例,用于接收Keycloak推送的事件:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserDisabledController {

    @PostMapping("/api/user/disabled-handler")
    public void handleUserDisabled(@RequestBody UserDisabledRequest request) {
        // 执行你的业务逻辑:记录日志、发送通知、冻结用户资源等
        System.out.println("用户[" + request.getUsername() + "](" + request.getUserId() + ")被临时禁用,原因:" + request.getDisableReason());
        // 此处添加具体业务操作,如更新数据库、调用内部服务等
    }

    public static class UserDisabledRequest {
        private String userId;
        private String username;
        private String disableReason;

        // Getter & Setter
        public String getUserId() { return userId; }
        public void setUserId(String userId) { this.userId = userId; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getDisableReason() { return disableReason; }
        public void setDisableReason(String disableReason) { this.disableReason = disableReason; }
    }
}

内容的提问来源于stack exchange,提问作者Самир Шахмурадлы

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 00:45:35