只读联合用户的Keycloak OTP

问题描述 投票:0回答:3

我已经为我们数据库中的联合用户实现了自定义用户存储提供程序。

我想通过 keycloak 为这些用户管理 OTP,当我在流程中将 OTP 设置为必需并将 OTP 配置为必需操作时,联合用户登录后会显示 OTP 表单,但是当我尝试设置 OTP 时,我收到错误用户对于此更新是只读的。

如何允许只读联合用户通过 keycloak 进行 OTP 配置?

2022-01-31 17:00:12,704 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-669) Uncaught server error: org.keycloak.storage.ReadOnlyException: user is read only for this update
at [email protected]//org.keycloak.storage.adapter.AbstractUserAdapter.removeRequiredAction(AbstractUserAdapter.java:77)
at [email protected]//org.keycloak.services.resources.LoginActionsService.processRequireAction(LoginActionsService.java:1044)
at [email protected]//org.keycloak.services.resources.LoginActionsService.requiredActionPOST(LoginActionsService.java:967)

用户适配器是

public class UserAdminAdapter extends AbstractUserAdapter {
    
  private final CustomUser user;
    
  public UserAdminAdapter(
    KeycloakSession session,
    RealmModel realm,
    ComponentModel storageProviderModel,
    CustomUser user) {
        super(session, realm, storageProviderModel);
        this.user = user;
  }
    
  @Override
  public String getUsername() {
    return user.getUsername();
  }
    
  @Override
  public Stream<String> getAttributeStream(String name) {
        Map<String, List<String>> attributes = getAttributes();
        return (attributes.containsKey(name)) ? attributes.get(name).stream() : Stream.empty();
  }
    
  @Override
  protected Set<GroupModel> getGroupsInternal() {
    if (user.getGroups() != null) {
        return user.getGroups().stream().map(UserGroupModel::new).collect(Collectors.toSet());
    }
    return new HashSet<>();
  }
    
  @Override
  protected Set<RoleModel> getRoleMappingsInternal() {
    if (user.getRoles() != null) {
        return user.getRoles().stream().map(roleName -> new UserRoleModel(roleName, realm)).collect(Collectors.toSet());
    }
    return new HashSet<>();
  }
    
  @Override
  public boolean isEnabled() {
    return user.isEnabled();
  }
    
  @Override
  public String getId() {
    return StorageId.keycloakId(storageProviderModel, user.getUserId() + "");
  }
    
  @Override
  public String getFirstAttribute(String name) {
    List<String> list = getAttributes().getOrDefault(name, Collections.emptyList());
    return list.isEmpty() ? null : list.get(0);
  }
    
  @Override
  public Map<String, List<String>> getAttributes() {
    MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
    attributes.add(UserModel.USERNAME, getUsername());
    attributes.add(UserModel.EMAIL, getEmail());
    attributes.add(UserModel.FIRST_NAME, getFirstName());
    attributes.add(UserModel.LAST_NAME, getLastName());
    attributes.addAll(user.getAttributes());
    return attributes;
  }
    
  @Override
  public String getFirstName() {
    return user.getFirstName();
  }
    
  @Override
  public String getLastName() {
    return user.getLastName();
  }
    
  @Override
  public String getEmail() {
    return user.getEmail();
  }
}
keycloak federation totp
3个回答
4
投票

原因是在你的

UserAdminAdapter
类中,你没有实现
removeRequiredAction
addRequiredAction
方法。您收到的消息来自基类提供的默认实现。您应该自己实现这些方法并将所需的操作存储在底层存储中,或者考虑从
AbstractUserAdapterFederatedStorage
扩展您的类,而不是将所有此类功能委托给内部 Keycloak 实现。


2
投票

我的外部数据库中的完整 OTP 支持

好吧,一个多星期后,我终于在 Keycloak 18.0 上使用了它。您需要做什么?简单地说,您必须实现身份验证工作流程中的每一步:

  1. 创建您的用户存储 SPI
  2. 实施凭证更新 SPI
  3. 实现自定义凭证提供程序 SPI
  4. 实现自定义所需操作 SPI
  5. 实施您的验证器 SPI
  6. 实施您的表格(我有点使用 KC 的内部 OTP 表格)
  7. 启用您所需的操作
  8. 创建浏览器工作流程的副本并粘贴您的身份验证器

我们能得到什么?

  1. 我们获得了完全可定制的 OTP 身份验证器(领域的政策待定......)
  2. 您可以使用该代码在您的应用程序中进行验证(它位于您的数据库中)
  3. 您可以在应用程序中设置用户进行 OTP 身份验证(不涉及 KC 管理页面,因此,您可以将管理页面保留在防火墙之外)

在我看来,这有点烦人,因为我们必须进行很多循环才能在本地存储数据以及如何处理集成的 OTP 表单(为了“自然的外观”),但它给出了我完全控制我的 OTP 集成,而且,我可以备份我的数据库,并且它们的 OTP 身份验证仍然存在,因此,如果我在 KC 升级中失败或它被损坏,我仍然拥有所有数据。

最后,当您的经理拥有自定义 OTP 身份验证时,这应该是这样的enter image description here


0
投票

我可以提供一个更简单的解决方案,该解决方案目前正在生产中。我所做的唯一改变是实施

UserStorageProvider
。因此,我扩展了以下方法:
supportsCredentialType
isConfiguredFor
isValid
。我的
UserAdapter
使用
UserCredentialManager
作为
credentialManager
方法。

Keycloak 从版本 23 开始运行,我注意到浏览器身份验证流程支持条件 OTP 流程。

以下是我的实现中提到的方法:

    @Override
    public boolean supportsCredentialType(String credentialType) {
        return PasswordCredentialModel.TYPE.equals(credentialType) || OTPCredentialModel.TYPE.equals(credentialType);
    }

    @Override
    public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
        return switch (credentialType) {
            case PasswordCredentialModel.TYPE -> true;
            case OTPCredentialModel.TYPE -> client.isConfiguredFor(user.getEmail(), credentialType);
            default -> false;
        };
    }

    @Override
    public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) {
        if (!this.supportsCredentialType(credentialInput.getType()) || !(credentialInput instanceof UserCredentialModel)) {
            return false;
        }

        return client.validate(user.getEmail(), credentialInput.getChallengeResponse(), credentialInput.getType());
    }

这意味着不需要自定义凭据更新 SPI、不需要所需操作 SPI、不需要身份验证器 SPI,也不需要自定义身份验证流程。

完整的实现可以在这里找到:https://github.com/systemli/userli-keycloak-provider

© www.soinside.com 2019 - 2024. All rights reserved.