add bidirectional sync for user enabled status

Keycloak → LDAP sync via UserModelDelegate proxy in setEnabled()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yongjie Xu
2026-04-20 15:13:01 +08:00
co-authored by Claude Opus 4.6
parent 870ebbf7d6
commit 7f171b6bf7
@@ -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);
}
}