调整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. 可选优化:失败次数超时重置
如果需要在一段时间后自动重置失败次数,可以添加定时任务或在用户下次登录时检查最后失败时间:
- 在用户属性中额外存储
lastFailedLoginTime记录最后失败时间 - 在
handleFailedLogin方法中,先判断当前时间与最后失败时间的间隔,超过阈值则重置失败次数为1 - 或者使用Keycloak的定时任务扩展,定期清理过期的失败次数记录
内容的提问来源于stack exchange,提问作者anonymous
相关产品推荐
相关产品推荐

