在 Java 中使用 Keycloak 搜索用戶
一、概述
Keycloak 是一個第三方身份和訪問管理解決方案,可以幫助我們將身份驗證和授權集成到我們的應用程序中。
在本教程中,我們將看到一些在 Keycloak 中搜索用戶的示例。
2.密鑰斗篷配置
首先,我們需要配置Keycloak。讓我們創建一個名為baeldung
的初始管理員用戶,密碼為secretPassword
。其次,我們需要一個工作的境界。讓我們使用啟動 Keycloak 時已經存在的master
域。
最後,我們需要一個可以從我們的 Spring Boot 應用程序中使用的客戶端。對於此示例,讓我們使用默認創建的admin-cli
客戶端。
我們需要領域中的一些用戶,以便我們稍後可以搜索他們。讓我們創建一個用戶,用戶名為“ user1
”,電子郵件為“ [email protected]
”,名稱為“ First UserI
”。現在,我們可以將此模式再重複幾次,總共有 10 個用戶。
3.Keycloak管理客戶端
Keycloak 有一個REST API ,可用於訪問管理控制台 UI 上可用的所有功能。我們可以將它與我們喜歡的任何客戶端一起使用,但Keycloak 提供了一個Java 客戶端**,使它更容易。在我們的第一個示例中,我們將從 Spring Boot 應用程序中使用這個 Java 客戶端**。
首先,讓我們將keycloak-admin-client
依賴項添加到我們的pom.xml
中:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>21.0.1</version>
</dependency>
確保**客戶端的版本與服務器的版本相匹配**很重要。不匹配的版本可能無法正常工作。
現在,讓我們在我們的應用程序中配置客戶端。為此,我們需要創建一個Keycloak
bean :
@Bean
Keycloak keycloak() {
return KeycloakBuilder.builder()
.serverUrl("http://localhost:8080")
.realm("master")
.clientId("admin-cli")
.grantType(OAuth2Constants.PASSWORD)
.username("baeldung")
.password("secretPassword")
.build();
}
在此構建器中,我們配置服務器 URL,設置前面提到的領域和客戶端名稱,並提供正確的憑據。
讓我們創建一個服務,它將使用這個 bean 來訪問我們的 Keycloak 服務器:
@Service
public class AdminClientService {
@Autowired
Keycloak keycloak;
@PostConstruct
void searchUsers() {
// ...
}
}
在此之後,讓我們開始我們的應用程序。現在我們可以開始搜索用戶了。
3.1.按用戶名搜索
Java 客戶端證明了一種通過用戶名搜索用戶的非常方便的方法。首先,我們需要引用我們想要使用的領域,然後訪問用戶並使用**searchByUsername()
方法**。讓我們創建一個搜索用戶並記錄結果的方法:
private static final String REALM_NAME = "master";
void searchByUsername(String username, boolean exact) {
logger.info("Searching by username: {} (exact {})", username, exact);
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.users()
.searchByUsername(username, exact);
logger.info("Users found by username {}", users.stream()
.map(user -> user.getUsername())
.collect(Collectors.toList()));
}
現在,我們可以從帶有不同參數的searchUsers()
方法中使用它:
void searchUsers() {
searchByUsername("user1", true);
searchByUsername("user", false);
searchByUsername("1", false);
}
第一次搜索查找與“ user1
”用戶名完全匹配的內容。第二個不需要完全匹配,因此它返回用戶名包含單詞“ user
”的所有用戶。第三個類似,查找包含數字“1”的用戶名。由於我們有 10 個用戶,用戶名從user1
到user10
,因此日誌包含以下結果:
12:20:22.295 [main] INFO cbkadminclient.AdminClientService - Searching users in Keycloak 21.0.1
12:20:22.296 [main] INFO cbkadminclient.AdminClientService - Searching by username: user1 (exact true)
12:20:22.341 [main] INFO cbkadminclient.AdminClientService - Users found by username [user1]
12:20:22.341 [main] INFO cbkadminclient.AdminClientService - Searching by username: user (exact false)
12:20:22.357 [main] INFO cbkadminclient.AdminClientService - Users found by username [user1, user10, user2, user3, user4, user5, user6, user7, user8, user9]
12:20:22.357 [main] INFO cbkadminclient.AdminClientService - Searching by username: 1 (exact false)
12:20:22.369 [main] INFO cbkadminclient.AdminClientService - Users found by username [user1, user10]
3.2.通過電子郵件搜索
正如我們之前看到的,根據用戶名進行過濾非常容易。幸運的是,使用電子郵件地址作為過濾器與此非常相似。讓我們使用searchByEmail()
方法接受一個電子郵件地址和一個用於精確匹配的boolean
參數:
void searchByEmail(String email, boolean exact) {
logger.info("Searching by email: {} (exact {})", email, exact);
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.users()
.searchByEmail(email, exact);
logger.info("Users found by email {}", users.stream()
.map(user -> user.getEmail())
.collect(Collectors.toList()));
}
讓我們使用“ [email protected]
”地址對其進行測試,並尋找完全匹配的地址。這次只有一個結果:
12:24:16.130 [main] INFO cbkadminclient.AdminClientService - Searching by email: [email protected] (exact true)
12:24:16.141 [main] INFO cbkadminclient.AdminClientService - Users found by email [[email protected]]
3.3.按自定義屬性搜索
在 Keycloak 中,用戶也可以擁有自定義屬性,而不僅僅是簡單的用戶名和電子郵件。讓我們為user1
出生日期添加一個名為DOB
的自定義屬性,值為“2000-01-05”。
現在,我們可以使用管理客戶端的searchByAttributes()
方法:
void searchByAttributes(String query) {
logger.info("Searching by attributes: {}", query);
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.users()
.searchByAttributes(query);
logger.info("Users found by attributes {}", users.stream()
.map(user -> user.getUsername() + " " + user.getAttributes())
.collect(Collectors.toList()));
}
讓我們使用“ DOB:2000-01-05
”查詢來列出具有匹配屬性的用戶:
13:19:51.091 [main] INFO cbkadminclient.AdminClientService - Searching by attributes: DOB:2000-01-05
13:19:51.103 [main] INFO cbkadminclient.AdminClientService - Users found by attributes [user1 {DOB=[2000-01-05]}]
3.4.按組搜索
用戶也可以屬於一個組,我們也可以以此進行過濾。讓我們創建一個名為“ Test Group
”的組並向其中添加一些成員。現在,我們可以使用管理客戶端獲取該組的成員:
void searchByGroup(String groupId) {
logger.info("Searching by group: {}", groupId);
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.groups()
.group(groupId)
.members();
logger.info("Users found by group {}", users.stream()
.map(user -> user.getUsername())
.collect(Collectors.toList()));
}
但是,請務必注意,我們在這裡使用的是組 ID 而不是名稱。這樣,我們可以列出 Keycloak 組的成員:
14:35:09.275 [main] INFO cbkadminclient.AdminClientService - Searching by group: c67643fb-514e-488a-a4b4-5c0bdf2e7477
14:35:09.290 [main] INFO cbkadminclient.AdminClientService - Users found by group [user1, user2, user3, user4, user5]
3.5.按角色搜索
按角色搜索與前面的方法非常相似,因為我們可以像列出組成員一樣列出具有特定角色的用戶。為此,我們需要為我們的一些用戶分配一個角色。讓我們創建一個名為“user”
的角色並將其分配給“ user1
”。現在,我們可以實現搜索功能了:
void searchByRole(String roleName) {
logger.info("Searching by role: {}", roleName);
List<UserRepresentation> users = keycloak.realm(REALM_NAME)
.roles()
.get(roleName)
.getUserMembers();
logger.info("Users found by role {}", users.stream()
.map(user -> user.getUsername())
.collect(Collectors.toList()));
}
讓我們看看哪些用戶具有此角色:
12:03:23.788 [main] INFO cbkadminclient.AdminClientService - Searching by role: user
12:03:23.802 [main] INFO cbkadminclient.AdminClientService - Users found by role [user1]
4. 自定義 REST 端點
正如我們所見,Keycloak 默認提供有用的搜索功能,但在某些情況下,我們可能需要一些不同的或更複雜的東西。但是也有一個解決方案。我們可以通過向 Keycloak 添加新的 API 端點來實現我們自己的自定義功能。
假設我們想要查找屬於特定組並具有特定角色的用戶。例如,我們可以在“ software development department
”組中找到所有具有“ project manager
”角色的用戶。
首先,我們需要一個從 Keycloak 的RealmResourceProvider
擴展的新類。讓我們在這裡實現我們的自定義功能:
public class KeycloakUserApiProvider implements RealmResourceProvider {
private final KeycloakSession session;
public KeycloakUserApiProvider(KeycloakSession session) {
this.session = session;
}
public void close() {
}
public Object getResource() {
return this;
}
@GET
@Produces({ MediaType.APPLICATION_JSON })
public Stream<UserRepresentation> searchUsersByGroupAndRoleName(@QueryParam("groupName") @NotNull String groupName, @QueryParam("roleName") @NotBlank String roleName) {
RealmModel realm = session.getContext().getRealm();
Optional<GroupModel> groupByName = session.groups()
.getGroupsStream(realm)
.filter(group -> group.getName().equals(groupName))
.findAny();
GroupModel group = groupByName.orElseThrow(() -> new NotFoundException("Group not found with name " + groupName));
return session.users()
.getGroupMembersStream(realm, group)
.filter(user -> user.getRealmRoleMappingsStream().anyMatch(role -> role.getName().equals(roleName)))
.map(user -> ModelToRepresentation.toBriefRepresentation(user));
}
}
在此之後,我們需要通過配置一個RealmResourceProviderFactory
來告訴 Keycloak 使用這個類:
public class KeycloakUserApiProviderFactory implements RealmResourceProviderFactory {
public static final String ID = "users-by-group-and-role-name";
@Override
public RealmResourceProvider create(KeycloakSession session) {
return new KeycloakUserApiProvider(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}
最後,我們需要通過在META-INF
文件夾中創建一個文件來註冊這個類。此文件中應該只有一行包含我們類的限定名稱。讓我們創建**src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
**文件,使其包含我們類的名稱:
com.baeldung.keycloak.customendpoint.KeycloakUserApiProviderFactory
讓我們構建項目並將jar 複製到 Keycloak 中的providers
文件夾,然後運行build
命令來更新服務器的 provider 註冊表:
kc build
我們得到以下輸出,這意味著我們的自定義提供程序已被識別和註冊:
Updating the configuration and installing your custom providers, if any. Please wait.
Server configuration updated and persisted. Run the following command to review the configuration:
kc.bat show-config
讓我們再次啟動 Keycloak 實例並在[http://localhost:8080/realms/master/users-by-group-and-role-name?groupName=Test%20Group&roleName=user](http://localhost:8080/realms/master/users-by-group-and-role-name?groupName=Test%20Group&roleName=user) .
此 API 端點成功返回滿足兩個條件的用戶,這意味著它屬於“ Test Group
”並具有角色“ user
”:
[
{
"id": "2c59a20f-df38-4d14-8ff9-067ea30f7937",
"createdTimestamp": 1678099525313,
"username": "user1",
"enabled": true,
"emailVerified": true,
"firstName": "First",
"lastName": "User",
"email": "[email protected]"
}
]
5.結論
在本文中,我們將 Spring Boot 應用程序與 Keycloak 集成在一起,使用 Keycloak Admin Client 來管理用戶。這提供了一種訪問現有功能的簡單方法。然後,我們創建了一個自定義 REST 端點以使用我們的自定義功能擴展 Keycloak,使我們能夠實現任何必要的自定義邏輯。
與往常一樣,這些示例的源代碼可在 GitHub 上獲得。