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

调整Keycloak登录失败邮件通知:仅第二次失败后触发

实现Keycloak登录失败二次触发邮件通知的修改方案

要实现仅在第二次登录失败后发送邮件通知,我们需要自定义Keycloak事件监听器,通过用户属性记录登录失败次数,达到阈值后再触发邮件通知。以下是具体实现步骤:

1. 编写自定义事件监听器

Keycloak默认的登录失败通知监听器没有次数判断逻辑,我们需要实现一个自定义的EventListenerProvider来处理登录失败/成功事件:

监听器核心代码

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.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.email.EmailException;
import org.jboss.logging.Logger;

public class CustomFailedLoginListener implements EventListenerProvider {
    private static final Logger LOG = Logger.getLogger(CustomFailedLoginListener.class);
    private final KeycloakSession session;

    public CustomFailedLoginListener(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public void onEvent(Event event) {
        // 处理登录失败事件
        if (EventType.LOGIN_FAILED.equals(event.getType())) {
            handleFailedLogin(event);
        }
        // 登录成功时重置失败次数
        else if (EventType.LOGIN_SUCCESS.equals(event.getType())) {
            resetFailedAttempts(event);
        }
    }

    private void handleFailedLogin(Event event) {
        String userId = event.getUserId();
        if (userId == null) return;

        RealmModel realm = session.getContext().getRealm();
        UserModel user = session.users().getUserById(realm, userId);
        if (user == null) return;

        // 获取当前失败次数,默认从1开始计数
        Integer failedAttempts = (Integer) user.getFirstAttribute("failedLoginAttempts");
        failedAttempts = (failedAttempts == null) ? 1 : failedAttempts + 1;

        // 更新用户属性中的失败次数
        user.setSingleAttribute("failedLoginAttempts", failedAttempts.toString());

        // 仅当失败次数≥2时触发邮件通知
        if (failedAttempts >= 2) {
            sendFailureNotificationEmail(user, realm);
        }
    }

    private void resetFailedAttempts(Event event) {
        String userId = event.getUserId();
        if (userId == null) return;

        RealmModel realm = session.getContext().getRealm();
        UserModel user = session.users().getUserById(realm, userId);
        if (user != null) {
            user.removeAttribute("failedLoginAttempts");
        }
    }

    private void sendFailureNotificationEmail(UserModel user, RealmModel realm) {
        EmailTemplateProvider emailProvider = session.getProvider(EmailTemplateProvider.class);
        try {
            // 复用Keycloak内置的登录失败邮件模板
            emailProvider.sendEventEmail(EventType.LOGIN_FAILED, user, realm, null);
        } catch (EmailException e) {
            LOG.error("Failed to send login failure email to user: " + user.getUsername(), e);
        }
    }

    @Override
    public void onEvent(AdminEvent event, boolean includeRepresentation) {}

    @Override
    public void close() {}
}

监听器工厂类

需要实现对应的工厂类来注册监听器:

import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;

public class CustomFailedLoginListenerFactory implements EventListenerProviderFactory {

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

    @Override
    public void init(org.keycloak.Config.Scope config) {}

    @Override
    public void postInit(KeycloakSessionFactory factory) {}

    @Override
    public void close() {}

    @Override
    public String getId() {
        return "custom-failed-login-listener"; // 监听器唯一标识,后台配置时需要用到
    }
}

2. 打包与部署

  • 将上述代码打包成JAR文件,放入Keycloak安装目录下的providers文件夹
  • 执行Keycloak构建命令:kc.sh build(Linux/Mac)或kc.bat build(Windows)
  • 启动Keycloak服务,进入后台管理界面

3. 配置监听器

  • 进入目标Realm的设置页面,选择Events -> Event Listeners
  • 移除默认的login-failure-notification监听器
  • 添加我们自定义的custom-failed-login-listener监听器
  • 保存配置后,测试登录失败场景:第一次失败无邮件,第二次及之后失败会触发邮件通知

4. 可选优化:失败次数超时重置

如果需要在一段时间后自动重置失败次数,可以添加定时任务或在用户下次登录时检查最后失败时间:

  1. 在用户属性中额外存储lastFailedLoginTime记录最后失败时间
  2. 在handleFailedLogin方法中,先判断当前时间与最后失败时间的间隔,超过阈值则重置失败次数为1
  3. 或者使用Keycloak的定时任务扩展,定期清理过期的失败次数记录

内容的提问来源于stack exchange,提问作者anonymous

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 20:08:34