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

Keycloak v26.3.3自定义存储提供商联邦用户列表分页失效

自定义Keycloak用户存储SPI分页异常问题

我为本地部署的Keycloak实现了一个自定义存储提供商SPI,该SPI可正常激活。进入用户菜单选项时,能正常列出前x个用户,但点击下一页图标后,没有用户显示。

需要说明的是,我采用数据库端分页,但用户提供商的getUsersCount()方法从未被调用。

以下是我的用户提供商实现代码:

//package and imports removed for clarity

public class ConsaludSdUserProvider implements UserStorageProvider, UserLookupProvider, UserQueryProvider, CredentialInputValidator, CredentialInputUpdater {
    private static final Logger LOG = Logger.getLogger(ConsaludSdUserProvider.class);

    private final KeycloakSession session;
    private final ComponentModel model;
    private final ISdUsuariosClient client;
    private final Argon2idEncryptor encryptor;

    protected Map<String, UserModel> loadedUsers = new HashMap<>();

    public ConsaludSdUserProvider(KeycloakSession session, ComponentModel model) {
        this.session = session;
        this.model = model;
        this.client = new PostgresSdUsuariosClient(model.get(Constants.CONN_STRING_FIELD_NAME), model.get(Constants.USERID_FIELD_NAME), model.get(Constants.PASSWORD_FIELD_NAME));
        this.encryptor = new Argon2idEncryptor(model.get(Constants.HASH_SECRET_FIELD_NAME));
    }

    //region UserStorageProvider
    //- removed for clarity
    //endregion

    //region UserLookupProvider
    @Override
    public UserModel getUserById(RealmModel realmModel, String s) {
        LOG.info("getUserById invocado. ID: '" + s + "'");
        try {
            var adapter = loadedUsers.get(s);
            if (adapter != null) {
                return adapter;
            }
            var external_id = getRutFromKeycloakId(s);
            var entity = client.getUsuarioByRut(external_id);
            if (entity == null) {
                LOG.info("No se encontró usuario con id: " + s);
                return null;
            }
            adapter = new DatosUsuarioUserAdapter(session, realmModel, model, entity);
            loadedUsers.put(s, adapter);
            return adapter;
        } catch (SdUsuariosClientException e) {
            LOG.error("Error al traer usuario por RUT", e);
            return null;
        }
    }

    @Override
    public UserModel getUserByUsername(RealmModel realmModel, String s) {
        LOG.info("getUserByUsername invocado. Username: '" + s + "'");
        try {
            var adapter = loadedUsers.get(s);
            if (adapter != null) {
                return adapter;
            }
            var external_id = Integer.parseInt(s);
            var entity = client.getUsuarioByRut(external_id);
            if (entity == null) {
                LOG.info("No se encontró usuario con username: " + s);
                return null;
            }
            adapter = new DatosUsuarioUserAdapter(session, realmModel, model, entity);
            loadedUsers.put(s, adapter);
            return adapter;
        } catch (SdUsuariosClientException e) {
            LOG.error("Error al traer usuario por RUT", e);
            return null;
        } catch (NumberFormatException e) {
            LOG.error("Error al convertir ID proporcionado a un RUT", e);
            return null;
        }
    }

    @Override
    public CredentialValidationOutput getUserByCredential(RealmModel realm, CredentialInput input) {
        LOG.info("getUserByCredential invocado. Credential input: '" + input.getType() + "'");
        return UserLookupProvider.super.getUserByCredential(realm, input);
    }

    @Override
    public UserModel getUserByEmail(RealmModel realmModel, String s) {
        LOG.info("getUserByEmail invocado. Email: '" + s + "'");
        try {
            var entity = client.getUsuarioPorEmail(s);
            if (entity == null) {
                LOG.info("No se encontró usuario con email: " + s);
                return null;
            }
            return new DatosUsuarioUserAdapter(session, realmModel, model, entity);
        } catch (SdUsuariosClientException e) {
            LOG.error("Error al traer usuario por RUT", e);
            return null;
        }
    }
    //endregion

    //region UserQueryProvider
    @Override
    public Stream<UserModel> searchForUserStream(RealmModel realmModel, Map<String, String> params, Integer firstResult, Integer maxResults) {
        var search_param = params.get(UserModel.SEARCH);
        LOG.info("searchForUserStream invocado. search_param: '" + search_param + "', fila inicial: " + firstResult + ", max results: " + maxResults);
        try {
            var datos = client.getUsuarios(search_param, firstResult, maxResults);
            if (datos == null) {
                LOG.info("No se encontraron usuarios por el parámetro de búsqueda indicado: " + search_param);
                return Stream.empty();
            }
            return datos.stream().map(r -> new DatosUsuarioUserAdapter(session, realmModel, model, r));
        } catch (SdUsuariosClientException e) {
            LOG.info("Error al tratar de encontrar usuarios por parámetro de búsqueda: " + search_param, e);
            return Stream.empty();
        }
    }

    @Override
    public Stream<UserModel> getGroupMembersStream(RealmModel realmModel, GroupModel groupModel, Integer firstResult, Integer maxResults) {
        LOG.info("getGroupMembersStream invocado. Fila inicial: " + firstResult + ", max results: " + maxResults);
        return Stream.empty();
    }

    @Override
    public Stream<UserModel> searchForUserByUserAttributeStream(RealmModel realmModel, String attrName, String attrValue) {
        LOG.info("searchForUserByUserAttributeStream invocado. Nombre atributo: " + attrName + ", valor atributo: " + attrValue);
        throw new NotImplementedException("");
    }

    @Override
    public int getUsersCount(RealmModel realm) {
        LOG.info("getUsersCount invocado.");
        try {
            return client.getUsuariosCount();
        } catch (SdUsuariosClientException e) {
            LOG.error("Error al traer conteo de usuarios", e);
            return 0;
        }
    }

    @Override
    public int getUsersCount(RealmModel realm, Set<String> groupIds) {
        LOG.info("getUsersCount con groupIds invocado.");
        return UserQueryProvider.super.getUsersCount(realm, groupIds);
    }
    //endregion

    //region CredentialInputValidator
    //- removed for clarity
    //endregion

    //region CredentialInputUpdater
    //- removed for clarity
    //endregion

    private Integer getRutFromKeycloakId(String keycloakId) {
        var external_id = StorageId.externalId(keycloakId);
        return Integer.parseInt(external_id);
    }
}

内容的提问来源于stack exchange,提问作者Léster

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 10:25:23