1. 概述
Keycloak 是一个第三方身份和访问管理解决方案,能帮我们快速集成认证授权功能。本文将通过几个示例,演示如何在 Keycloak 中高效搜索用户。
2. Keycloak 配置
首先需要完成 Keycloak 的基础配置:
✅ 创建初始管理员用户(用户名:baeldung
,密码:secretPassword
)
✅ 使用默认的 master
realm(无需新建)
✅ 使用默认的 admin-cli
客户端(无需新建)
✅ 创建 10 个测试用户(用户名格式:user1
到 user10
,邮箱格式:user1@example.com
)
⚠️ 确保用户邮箱格式一致,例如
user1@example.com
,避免后续搜索踩坑。
3. Keycloak Admin Client
Keycloak 提供 REST API 和 Java 客户端,后者能简化开发。这里我们通过 Spring Boot 集成 Java 客户端。
3.1. 添加依赖
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>21.0.1</version>
</dependency>
❌ 客户端版本必须与 Keycloak 服务器版本严格匹配,否则可能报错。
3.2. 配置客户端
@Bean
Keycloak keycloak() {
return KeycloakBuilder.builder()
.serverUrl("http://localhost:8080")
.realm("master")
.clientId("admin-cli")
.grantType(OAuth2Constants.PASSWORD)
.username("baeldung")
.password("secretPassword")
.build();
}
3.3. 创建服务类
@Service
public class AdminClientService {
@Autowired Keycloak keycloak;
@PostConstruct
void searchUsers() {
// 后续搜索方法将在此调用
}
}
3.4. 按用户名搜索
private static final String REALM_NAME = "master";
void searchByUsername(String username, boolean exact) {
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.users()
.searchByUsername(username, exact);
logger.info("匹配用户: {}", users.stream()
.map(UserRepresentation::getUsername)
.collect(Collectors.toList()));
}
测试用例:
searchUsers() {
searchByUsername("user1", true); // 精确匹配
searchByUsername("user", false); // 模糊匹配
searchByUsername("1", false); // 包含"1"的用户
}
输出结果:
精确匹配 user1: [user1]
模糊匹配 user: [user1, user10, user2, user3, user4, user5, user6, user7, user8, user9]
包含"1"的用户: [user1, user10]
3.5. 按邮箱搜索
void searchByEmail(String email, boolean exact) {
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.users()
.searchByEmail(email, exact);
logger.info("匹配邮箱: {}", users.stream()
.map(UserRepresentation::getEmail)
.collect(Collectors.toList()));
}
测试用例:
searchByEmail("user1@example.com", true);
输出结果:
匹配邮箱: [user1@example.com]
3.6. 按自定义属性搜索
假设用户 user1
添加了自定义属性 DOB:2000-01-05
:
void searchByAttributes(String query) {
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.users()
.searchByAttributes(query);
logger.info("匹配属性: {}", users.stream()
.map(user -> user.getUsername() + " " + user.getAttributes())
.collect(Collectors.toList()));
}
测试用例:
searchByAttributes("DOB:2000-01-05");
输出结果:
匹配属性: [user1 {DOB=[2000-01-05]}]
3.7. 按组搜索
void searchByGroup(String groupId) {
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.groups()
.group(groupId)
.members();
logger.info("组成员: {}", users.stream()
.map(UserRepresentation::getUsername)
.collect(Collectors.toList()));
}
⚠️ 需使用组 ID(非组名),例如
c67643fb-514e-488a-a4b4-5c0bdf2e7477
输出结果:
组成员: [user1, user2, user3, user4, user5]
3.8. 按角色搜索
void searchByRole(String roleName) {
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.roles()
.get(roleName)
.getUserMembers();
logger.info("角色成员: {}", users.stream()
.map(UserRepresentation::getUsername)
.collect(Collectors.toList()));
}
测试用例:
searchByRole("user");
输出结果:
角色成员: [user1]
4. 自定义 REST 接口
当内置搜索无法满足需求时(如同时按组和角色筛选),可通过扩展 Keycloak 实现自定义接口。
4.1. 创建资源提供者
public class KeycloakUserApiProvider implements RealmResourceProvider {
private final KeycloakSession session;
public KeycloakUserApiProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void close() {}
@Override
public Object getResource() {
return this;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Stream<UserRepresentation> searchUsersByGroupAndRole(
@QueryParam("groupName") @NotNull String groupName,
@QueryParam("roleName") @NotBlank String roleName) {
RealmModel realm = session.getContext().getRealm();
GroupModel group = session.groups()
.getGroupsStream(realm)
.filter(g -> g.getName().equals(groupName))
.findAny()
.orElseThrow(() -> new NotFoundException("组未找到: " + groupName));
return session.users()
.getGroupMembersStream(realm, group)
.filter(user -> user.getRealmRoleMappingsStream()
.anyMatch(role -> role.getName().equals(roleName)))
.map(ModelToRepresentation::toBriefRepresentation);
}
}
4.2. 创建提供者工厂
public class KeycloakUserApiProviderFactory implements RealmResourceProviderFactory {
public static final String ID = "users-by-group-and-role";
@Override
public RealmResourceProvider create(KeycloakSession session) {
return new KeycloakUserApiProvider(session);
}
@Override
public String getId() {
return ID;
}
// 其他空实现方法省略...
}
4.3. 注册提供者
在 META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
文件中声明:
com.yourpackage.KeycloakUserApiProviderFactory
4.4. 部署与测试
- 打包为 JAR 并放入 Keycloak 的
providers
目录 - 执行构建命令:
kc build
- 重启 Keycloak
- 访问接口:
GET /realms/master/users-by-group-and-role?groupName=Test%20Group&roleName=user
返回结果:
[{
"id": "2c59a20f-df38-4d14-8ff9-067ea30f7937",
"username": "user1",
"email": "user1@example.com",
"firstName": "First",
"lastName": "User"
}]
5. 总结
✅ 使用 Keycloak Admin Client 可快速实现基础用户搜索
✅ 通过自定义接口能灵活扩展复杂查询逻辑
⚠️ 注意客户端版本匹配和组 ID 使用细节
完整代码示例可在 GitHub 获取。