Keycloak 用户存储 SPI 实现
Posted
技术标签:
【中文标题】Keycloak 用户存储 SPI 实现【英文标题】:Keycloak User Storage SPI Implementation 【发布时间】:2019-12-19 23:41:12 【问题描述】:我正在尝试实现自定义 keycloack Authenticator SPI 以针对外部数据源进行身份验证。 Spring boot Rest Service 也有,我也可以用。
我要解决的用例是
向用户显示 keycloak 登录屏幕。 Onsubmission 用户已针对外部数据源进行验证。
从外部数据源检索一些属性,将其映射到 keycloak 的 id 和访问令牌。
还设置了同一用户同时登录多次的用户限制条件。
我在想,这可以通过检索 keycloak 数据源中可用的用户会话信息来解决。如果我使用外部数据源,keycloak 是否仍然维护会话信息?
我遵循了官方指南(https://www.keycloak.org/docs/latest/server_development/index.html#_auth_spi_walkthrough)的第 8.3 节,这与我需要的非常相似。
现在我按照第 11 节跳过并开始(https://www.keycloak.org/docs/latest/server_development/index.html#_user-storage-spi)似乎也更合适。
我从实现自定义身份验证器 SPI 开始,认为这不是正确的方法,现在实现了 UserStorageProvider。
/***
* From UserLookupProvider
*/
public UserModel getUserById(String id, RealmModel realm)
System.out.println("ID: " + id + ":REALM:" + realm);
StorageId storageId = new StorageId(id);
/**
* StorageId.getExternalId() method is invoked to obtain
* the username embeded in the id parameter
*/
String username = storageId.getExternalId();
System.out.println("Name:" + username);
return getUserByUsername(username, realm);
/***
* From UserLookupProvider
* This method is invoked by the Keycloak login page when a user logs in
*/
public UserModel getUserByUsername(String username, RealmModel realm)
System.out.println("USERNAME: " + username + ":REALM:" + realm);
UserModel userModel = loadedUsers.get(username);
if (userModel == null)
String password = properties.getProperty(username);
if (password != null)
userModel = createUserModel(realm, username);
System.out.println("New UserModel:");
System.out.println(userModel.toString());
loadedUsers.put(username, userModel);
return userModel;
protected UserModel createUserModel(RealmModel realm, String username)
return new AbstractUserAdapter(session, realm, model)
@Override
public String getUsername()
return username;
;
关注文档(https://www.keycloak.org/docs/latest/server_development/index.html#packaging-and-deployment-2)
我们的提供者实现的类文件应该放在一个 jar 中。您还必须在 META-INF/services/org.keycloak.storage.UserStorageProviderFactory 文件中声明提供程序工厂类。
这里的问题是:我创建的jar在“META-INF”文件夹中没有服务目录,我需要手动创建并添加它吗?
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory 创建 jar 后,您可以使用常规 WildFly 部署它,方法是:将 jar 复制到 deploy/ 目录或使用 JBoss CLI。
使用 maven 创建 jar 后,将 jar 复制到“keycloak-6.0.1\standalone\deployments”文件夹。但我在“用户联合列表”中没有看到我的提供商
任何建议/帮助将不胜感激!
提前感谢您的建议。
【问题讨论】:
【参考方案1】:如果有人遇到这样的问题:
由于 META-INF/services 文件夹,未显示 UserStorage SPI。文档中提供了,但不清楚
在src/main/resources中,创建文件夹结构META-INF/services
在 META-INF/services 目录中创建一个名为 org.keycloak.storage.UserStorageProviderFactory 的文件(整个就是文件名)。它的内容是 SPI 的完全限定类名: com.test.UserSpi
【讨论】:
你能分享所有文件的整个目录结构吗?还尝试创建 UserStorage SPI。【参考方案2】:好的,您已澄清您需要一个用户存储提供程序 API。很棒
现在关于您的第二个“问题/挑战”:
从外部数据源检索一些属性,将其映射到 keycloak 的 id 和访问令牌。需要检索用户唯一 id 并将其作为主题 id 添加到 jwt 中。那就是 id,当这个令牌被传递给其他服务时,其余服务可以使用它来检索 id。
为此,您能做的最好的事情是:
将这些用户的唯一数据添加为用户属性(在管理控制台上查看)
在 Keycloak 上创建一个“客户端范围”,并为“用户属性”创建一个映射器 将您想要(从您的用户)添加的那些属性映射到您的 Id-token 和 access-token。您还需要将您的客户与您刚刚创建的“客户范围”联系起来。这可能听起来有点令人困惑,但这个视频是很好的材料,我相信它会对你有很大帮助:https://www.youtube.com/watch?v=ZxpY_zZ52kU(大约在 6:30 左右,你会看到如何为你的令牌添加额外的用户信息)
还可以查看此页面:https://jwt.io/(当您粘贴编码标记时,您可以看到它们的内容),它是开发人员的绝佳工具。
当您提出解决方案时,我将帮助您完成独特的会话,或者您将其作为不同的问题发布,因为那是不同的问题。
希望对你有帮助。
【讨论】:
@tony-008 对于独特的用户会话,我发布了一个单独的问题(***.com/questions/57643410)。请让我知道您的想法/建议。 您对 Keycloak 用户会话限制有何看法。我已经使用 Admin Rest api 完成了它。获取管理员令牌并使用此端点(/auth/admin/realms/realmId/users/userId/sessions)来检索用户会话并对其进行验证。我觉得应该有更好的方法 @amj 。我还没有实现这个,但如果我必须这样做,我的方法将如下:如果你去管理领域/会话部分,你会看到打开会话的数量每个用户客户端/用户。应该可以以编程方式查询它们。在您的 ProviderStore 的自定义 validate() 方法中,我将添加一个检查条件,即相关用户需要有 0 个预先存在的会话,而不是更多(仅当您使用密码时,如果使用令牌则不是因为您想要如果用户会话已经存在,则避免创建令牌)。通过执行此令牌重新发行应该逐个发生。【参考方案3】:我不确定您需要什么。让我们首先区分身份验证 SPI(联合身份检查)与用户提供者 SPI(联合用户)。第一个(文档的第 8 节 - 更多地关注针对外部服务对用户进行身份验证 - 类似于 facebook 或 google)。联合用户存储更像是您在具有传统“角色结构”的旧系统中拥有自己的用户,并且您基本上希望通过 keycloak 管理它们(通过导入它们或通过某些 API 查询 - 这将是部分该文件的 11 个)。所以请确定你真正需要的是什么。
第二,您提到以下内容:
> User is presented keycloak login screen. Onsubmission User is
> validated against external Datasource.
>
> Retrieve some attributes from external datasource, map it to
> keycloak's id and access token.
>
> Also put in a condition of user restriction of same user logging in
> multiple times at the same time.
>
> I was thinking, it could be solved by retrieving user session
> information that's available in the keycloak datasource. If i use
> external datasource, does keycloak still maintain session information?
你的意思是:从外部数据源检索一些属性,将其映射到 keycloak 的 id 和访问令牌。?通常你只检索用户核心信息,可能还有角色和其他自定义属性(不是会话信息)。 Keycloak 本身作为基于 openIDConnect 的授权服务器,将生成访问令牌,其中已经包含有关哪些人可以访问哪些受保护资源的信息,因此您实际上不需要从其他地方导入任何会话,或者您自己关心所述令牌的生成.
关于:还设置了同一用户登录的用户限制条件 在同一时间多次。当您记录您的客户第一次收到有效时间为 X 的 Bearer 令牌时,您到底想完成(或避免)什么,在那段时间内您不需要再次登录,直到令牌过期或被取消;再次是您的身份验证服务器负责的事情,而不是您实施的事情。你有更具体的要求吗?
我在想,这可以通过检索 keycloak 数据源中可用的用户会话信息来解决。如果我使用外部数据源,keycloak 是否仍然维护会话信息? 这听起来不对,你指的是什么会话数据?或者你需要访问?您的用户数据、范围、角色等可以通过 KEycloak Rest API (https://www.keycloak.org/docs-api/6.0/rest-api/index.html#_overview) 访问。您的外部数据源用于与用户相关的核心数据(不是外部会话),您认为为什么需要导入外部会话?
【讨论】:
感谢 @tony_008 的 cmets。我将发布多个 cmets 来回答您的问题。我很抱歉,这是因为字符限制 我从自定义身份验证器开始,用于针对外部数据源进行身份验证。正如您之前指出的那样,它不是为此而生的。现在,我开始实现 User Provider SPI。联合用户存储是我需要的。它现在可以工作了,但有一些小故障。 从外部数据源中获取一些属性,映射到keycloak的id和access token。需要获取用户的唯一id并将其作为主题id添加到jwt中。这就是 id,当这个令牌被传递给其他服务时,其他服务可以使用它来检索 id。 将其映射到 keycloak 的 id 和访问令牌。 我指的是 keycloak 的 openId 连接 id 令牌 还设置了同一用户同时登录多次的用户限制条件。我这里需要解决的是,我需要防止同样的情况用户同时从多个系统登录。所以,我在想最好的方法是在 sigin 处验证它们,并存储会话信息。如果有更好的验证和预防方法,请提出建议/评论。以上是关于Keycloak 用户存储 SPI 实现的主要内容,如果未能解决你的问题,请参考以下文章
如果为用户存储 SPI 选择“NO_CACHE”缓存策略,KeyCloak 7.0.0 会抛出 NullPointerException
在 Keycloak SPI/Provider 中获取当前用户访问令牌(初始登录时)
在自定义 Keycloak SPI Authenticator 中处理用户的取消