From 7f171b6bf7630a48bb35305f857199c647499570 Mon Sep 17 00:00:00 2001 From: Yongjie Xu Date: Mon, 20 Apr 2026 15:13:01 +0800 Subject: [PATCH] add bidirectional sync for user enabled status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keycloak → LDAP sync via UserModelDelegate proxy in setEnabled() Co-Authored-By: Claude Opus 4.6 --- .../keycloak/PwdLockToEnabledMapper.java | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/providers/PwdLockToEnabledMapper/src/main/java/com/example/keycloak/PwdLockToEnabledMapper.java b/providers/PwdLockToEnabledMapper/src/main/java/com/example/keycloak/PwdLockToEnabledMapper.java index 1f9667b..ed56164 100644 --- a/providers/PwdLockToEnabledMapper/src/main/java/com/example/keycloak/PwdLockToEnabledMapper.java +++ b/providers/PwdLockToEnabledMapper/src/main/java/com/example/keycloak/PwdLockToEnabledMapper.java @@ -8,19 +8,33 @@ import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; +import java.util.Collections; + public class PwdLockToEnabledMapper extends AbstractLDAPStorageMapper { + private static final String PWD_ACCOUNT_LOCKED_TIME = "pwdAccountLockedTime"; + // OpenLDAP 常用的永久锁定时间戳 + private static final String LDAP_LOCK_TIME = "000001010000Z"; + public PwdLockToEnabledMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider) { super(mapperModel, ldapProvider); } + // 逻辑封装:根据 Keycloak 用户状态更新 LDAP 对象 + private void setLdapStatus(LDAPObject ldapObject, UserModel userModel) { + if (!userModel.isEnabled()) { + // 如果用户被禁用,设置锁定时间 + ldapObject.setAttribute(PWD_ACCOUNT_LOCKED_TIME, Collections.singleton(LDAP_LOCK_TIME)); + } else { + // 如果用户被启用,移除锁定时间属性 + ldapObject.setAttribute(PWD_ACCOUNT_LOCKED_TIME, null); + } + } + @Override public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) { - // 1. 获取 pwdAccountLockedTime 属性 - String lockedTime = ldapUser.getAttributeAsString("pwdAccountLockedTime"); - - // 2. 逻辑判断:如果该属性存在,说明账号被锁定 - // 注意:OpenLDAP 中管理员禁用通常设置为 000001010000Z + String lockedTime = ldapUser.getAttributeAsString(PWD_ACCOUNT_LOCKED_TIME); + // 如果 LDAP 中有锁定时间,Keycloak 设置为禁用 if (lockedTime != null && !lockedTime.isEmpty()) { user.setEnabled(false); } else { @@ -30,19 +44,27 @@ public class PwdLockToEnabledMapper extends AbstractLDAPStorageMapper { @Override public void onRegisterUserToLDAP(LDAPObject ldapObject, UserModel userModel, RealmModel realmModel) { - // 如果需要双向同步(Keycloak 禁用同步到 LDAP),则在此编写逻辑 + // 同步初始状态到新创建的 LDAP 用户 + setLdapStatus(ldapObject, userModel); } @Override public UserModel proxy(LDAPObject ldapObject, UserModel userModel, RealmModel realmModel) { - return userModel; + // 关键:拦截 Keycloak 的更新操作 + return new org.keycloak.models.utils.UserModelDelegate(userModel) { + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + // 当在控制台切换"启用/禁用"开关时,同步到 LDAPObject + setLdapStatus(ldapObject, userModel); + // 提交更新到 LDAP + ldapProvider.getLdapIdentityStore().update(ldapObject); + } + }; } @Override public void beforeLDAPQuery(LDAPQuery query) { - // 关键修复:确保 LDAP 查询时请求了 pwdAccountLockedTime 属性 - // 否则 ldapUser.getAttributeAsString 将始终返回 null - query.addReturningLdapAttribute("pwdAccountLockedTime"); - query.addReturningReadOnlyLdapAttribute("pwdAccountLockedTime"); + query.addReturningLdapAttribute(PWD_ACCOUNT_LOCKED_TIME); } } \ No newline at end of file