一 , 二为官网内容简介, 三为楼主实战案例, 通过插件项目打成jar包去部署到keycloak二进制文件目录下完成jar包部署相关功能
目录
一, 简介
您可以使用用户存储SPI向Keycloak 写入扩展,以连接到外部用户数据库和凭证存储。内置的LDAP和ActiveDirectory支持是此SPI的实际实现。开箱即用,Keycloak 使用其本地数据库来创建、更新和查找用户并验证凭据。然而,组织通常拥有现有的外部专有用户数据库,无法迁移到Keycloak 的数据模型。对于这些情况,应用程序开发人员可以编写用户存储SPI的实现,以连接外部用户存储和内部用户对象模型,Keycloak 使用该模型登录用户并管理用户。
当Keycloak运行时需要查找一个用户时,例如当一个用户正在登录时,它会执行许多步骤来定位该用户。 它首先查看用户是否在用户缓存中; 如果找到用户,它就使用内存中的表示。 然后它在Keycloak本地数据库中查找用户。 如果没有找到用户,则循环用户存储SPI提供程序实现来执行用户查询,直到其中一个实现返回运行时正在寻找的用户。 提供者为用户查询外部用户存储,并将用户的外部数据表示映射到Keycloak的用户元模型。
二, 流程概述
1.提供者接口
在构建用户存储SPI的实现时,您必须定义一个提供者类和一个提供者工厂。 提供者类实例由提供者工厂为每个事务创建。 提供者类执行所有繁重的用户查找和其他用户操作。 它们必须实现org.keycloak.storage.UserStorageProvider接口。
package org.keycloak.storage;
public interface UserStorageProvider extends Provider {
/**
* Callback when a realm is removed. Implement this if, for example, you want to do some
* cleanup in your user storage when a realm is removed
*
* @param realm
*/
default
void preRemove(RealmModel realm) {
}
/**
* Callback when a group is removed. Allows you to do things like remove a user
* group mapping in your external store if appropriate
*
* @param realm
* @param group
*/
default
void preRemove(RealmModel realm, GroupModel group) {
}
/**
* Callback when a role is removed. Allows you to do things like remove a user
* role mapping in your external store if appropriate
* @param realm
* @param role
*/
default
void preRemove(RealmModel realm, RoleModel role) {
}
}
每个事务创建一次UserStorageProvider实例。事务完成后,调用UserStorageProvider.close()方法,然后对实例进行垃圾收集。实例由提供程序工厂创建。提供程序工厂实现org.keyclok.storage.UserStorageProviderFactory接口。
package org.keycloak.storage;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface UserStorageProviderFactory<T extends UserStorageProvider> extends ComponentFactory<T, UserStorageProvider> {
/**
* This is the name of the provider and will be shown in the admin console as an option.
*
* @return
*/
@Override
String getId();
/**
* called per Keycloak transaction.
*
* @param session
* @param model
* @return
*/
T create(KeycloakSession session, ComponentModel model);
...
}
在实现UserStorageProviderFactory时,提供者工厂类必须指定具体的提供者类作为模板参数。 这是必须的,因为运行时将对该类进行内省,以扫描其功能(它实现的其他接口)。 例如,如果你的提供者类名为FileProvider,那么工厂类应该是这样的:
public class FileProviderFactory implements UserStorageProviderFactory<FileProvider> {
public String getId() { return "file-provider"; }
public FileProvider create(KeycloakSession session, ComponentModel model) {
...
}
getId()方法返回用户存储提供程序的名称。 当您希望为特定领域启用提供者时,此id将显示在管理控制台的User Federation页面中。
create()方法负责分配提供者类的实例。 它接受一个org.keycloak.models.KeycloakSession参数。 此对象可用于查找其他信息和元数据,以及提供对运行时内各种其他组件的访问。 ComponentModel参数表示在特定领域中如何启用和配置提供者。 它包含已启用的提供程序的实例id,以及通过管理控制台启用时为其指定的任何配置。
2. 提供者其他功能接口
实现UserStorageProvider接口,可能会注意到它没有定义任何用于定位或管理用户的方法。 这些方法实际上是在其他功能接口中定义的 , 如果需要能够实现特性实现功能接口。 你可以实现以下这些接口:
SPI | 描述 |
org.keycloak.storage.user.UserLookupProvider | 外部存储的用户, 能登录keycloak就需要实现这个接口 |
org.keycloak.storage.user.UserQueryProvider | 实现后能从管理控制台查看和管理用户 |
org.keycloak.storage.user.UserRegistrationProvider | 实现后能从添加和删除用户 |
org.keycloak.storage.user.UserBulkUpdateProvider | 实现后支持一组用户的批量更新 |
org.keycloak.credential.CredentialInputValidator | 实现后可以编写验证密码的逻辑 |
org.keycloak.credential.CredentialInputUpdater | 实现后可以更新用户密码 |
3.用户模型接口
(1) 简介
功能接口中定义的大多数方法要么返回,要么以用户的表示形式传递。 这些表示是由org.keycloak.models.UserModel接口定义的。 应用程序开发人员需要实现这个接口。 它提供了外部用户存储和Keycloak使用的用户元模型之间的映射。
package org.keycloak.models;
public interface UserModel extends RoleMapperModel {
String getId();
String getUsername();
void setUsername(String username);
String getFirstName();
void setFirstName(String firstName);
String getLastName();
void setLastName(String lastName);
String getEmail();
void setEmail(String email);
...
}
UserModel实现提供了读取和更新关于用户的元数据的访问,包括用户名、姓名、电子邮件、角色和组映射,以及其他任意属性。
(2) 存储用户id
UserModel的一个重要方法是getId()方法。 在实现UserModel时,开发人员必须注意用户id格式。 格式必须为:
"f:" + component id + ":" + external id
例如: f:332a234e31234:wburke
Keycloak运行时经常需要根据用户id查找用户。 用户id包含足够的信息,因此运行时不必查询系统中的每个UserStorageProvider来查找用户。
4. 打包和部署
用户存储提供程序被打包在一个 JAR 中 , 将 JAR 复制到standalone/deployments/服务器目录部署
为了让Keycloak能够识别提供者,您需要在JAR中添加一个文件:META-INF/services/org.keycloak.storage.UserStorageProviderFactory。 这个文件必须包含一个行分隔的UserStorageProviderFactory实现的全限定类名列表:
示例:
org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
三, 实战案例
(1)表结构
Mysql数据库表
建表语句:
CREATE TABLE `sys_user` (
`user_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint(0) NULL DEFAULT NULL COMMENT '部门ID',
`login_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号',
`user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户昵称',
`user_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户 01注册用户)',
`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码',
`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像路径',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码',
`salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '盐加密',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`login_date` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间',
`pwd_update_date` datetime(0) NULL DEFAULT NULL COMMENT '密码最后更新时间',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 393 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;
表数据:
INSERT INTO `sys_user` VALUES (1, 103, 'zs', '张三', '00', 'zs@163.com', '15888888888', '1', '', '2e4d72b714790d7b37d4033d8d68be7a', '111111', '0', '0', '2021-09-17 09:24:47', '2021-09-03 19:58:47', 'admin', '2021-09-03 19:58:47', '', '2021-09-17 09:24:46', '管理员');
INSERT INTO `sys_user` VALUES (2, 105, 'ls', '李四', '00', 'ls@qq.com', '15666666666', '1', '', '782fbd827bfb1b8d89dda6697d60a875', '222222', '0', '0', '2021-09-03 19:58:47', '2021-09-03 19:58:47', 'admin', '2021-09-03 19:58:47', '', NULL, '测试员');
(2)相关依赖
<dependencies>
<!-- keycloak -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>15.0.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>15.0.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
(3)创建自定义用户表的实体UserEntity
先根据自己的表结构创建一个用户表对应的pojo类
(4)创建用户字段适配器 UserAdapter
用来把自定义的用户字段转换成keycloak能够识别的用户字段
(5)实现提供者类
实现以下接口 :
UserStorageProvider, 自定义用户必须要实现的接口
UserLookupProvider, 实现后,可以从keycloak进行登录
CredentialInputValidator, 实现后,可以更新密码
UserRegistrationProvider, 实现后,可以往自己数据库中增加删除修改用户数据
UserQueryProvider 实现后,可以从自己数据库中查询用户
实现数据库CRUD
UserMapper
数据库连接配置
system.properties
(6)实现工厂类
CustomUserStorageProviderFactory工厂类
读取配置,将配置加载到插件中,需要实现用户入口的工厂类
(7)添加插件的配置
要让插件正常使用,我们还需要添加一个配置文件,在项目的resources路径下,新建
META-INF\services\org.keycloak.storage.UserStorageProviderFactory
在org.keycloak.storage.UserStorageProviderFactory文件中写入刚才创建的入口factory类的路径
比如我们的CustomUserStorageProviderFactory,带完整包名
(8) 打包部署
将插件打包部署到keycloak 目录 standalone/deployments/ 下
(9)最后页面效果展示
(10)插件项目地址
https://gitee.com/its-not-ripe-yet/keycloak-service-user-storage.git