From ecad53efac21dc77ae7577d85a247e8a237592a0 Mon Sep 17 00:00:00 2001 From: AttilaUhrin Date: Fri, 31 Mar 2023 04:23:12 +0200 Subject: [PATCH] [BAEL-6161] Add example code for article. (#13651) Co-authored-by: Uhrin Attila --- .../spring-boot-keycloak-2/pom.xml | 39 ++++++++ .../adminclient/AdminClientService.java | 90 +++++++++++++++++++ .../baeldung/keycloak/adminclient/App.java | 30 +++++++ .../KeycloakUserApiProvider.java | 52 +++++++++++ .../KeycloakUserApiProviderFactory.java | 33 +++++++ ...ices.resource.RealmResourceProviderFactory | 1 + .../application-adminclient.properties | 1 + 7 files changed, 246 insertions(+) create mode 100644 spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/AdminClientService.java create mode 100644 spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/App.java create mode 100644 spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProvider.java create mode 100644 spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProviderFactory.java create mode 100644 spring-boot-modules/spring-boot-keycloak-2/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory create mode 100644 spring-boot-modules/spring-boot-keycloak-2/src/main/resources/application-adminclient.properties diff --git a/spring-boot-modules/spring-boot-keycloak-2/pom.xml b/spring-boot-modules/spring-boot-keycloak-2/pom.xml index 8b1eec2e4e..572986e8c4 100644 --- a/spring-boot-modules/spring-boot-keycloak-2/pom.xml +++ b/spring-boot-modules/spring-boot-keycloak-2/pom.xml @@ -16,6 +16,10 @@ 0.0.1-SNAPSHOT ../../parent-boot-2 + + + 21.0.1 + @@ -39,6 +43,41 @@ spring-boot-starter-test test + + + + org.keycloak + keycloak-admin-client + ${keycloak.version} + + + + org.keycloak + keycloak-core + provided + ${keycloak.version} + + + org.keycloak + keycloak-server-spi + ${keycloak.version} + provided + + + + org.keycloak + keycloak-server-spi-private + provided + ${keycloak.version} + + + org.keycloak + keycloak-services + provided + ${keycloak.version} + + + diff --git a/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/AdminClientService.java b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/AdminClientService.java new file mode 100644 index 0000000000..bacca9bdea --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/AdminClientService.java @@ -0,0 +1,90 @@ +package com.baeldung.keycloak.adminclient; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class AdminClientService { + + private static final Logger logger = LoggerFactory.getLogger(AdminClientService.class); + private static final String REALM_NAME = "master"; + + @Autowired + private Keycloak keycloak; + + @PostConstruct + public void searchUsers() { + logger.info("Searching users in Keycloak {}", keycloak.serverInfo() + .getInfo() + .getSystemInfo() + .getVersion()); + searchByUsername("user1", true); + searchByUsername("user", false); + searchByUsername("1", false); + searchByEmail("user2@test.com", true); + searchByAttributes("DOB:2000-01-05"); + searchByGroup("c67643fb-514e-488a-a4b4-5c0bdf2e7477"); + searchByRole("user"); + } + + private void searchByUsername(String username, boolean exact) { + logger.info("Searching by username: {} (exact {})", username, exact); + List users = keycloak.realm(REALM_NAME) + .users() + .searchByUsername(username, exact); + logger.info("Users found by username {}", users.stream() + .map(user -> user.getUsername()) + .collect(Collectors.toList())); + } + + private void searchByEmail(String email, boolean exact) { + logger.info("Searching by email: {} (exact {})", email, exact); + List users = keycloak.realm(REALM_NAME) + .users() + .searchByEmail(email, exact); + logger.info("Users found by email {}", users.stream() + .map(user -> user.getEmail()) + .collect(Collectors.toList())); + } + + private void searchByAttributes(String query) { + logger.info("Searching by attributes: {}", query); + List 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())); + } + + private void searchByGroup(String groupId) { + logger.info("Searching by group: {}", groupId); + List users = keycloak.realm(REALM_NAME) + .groups() + .group(groupId) + .members(); + logger.info("Users found by group {}", users.stream() + .map(user -> user.getUsername()) + .collect(Collectors.toList())); + } + + private void searchByRole(String roleName) { + logger.info("Searching by role: {}", roleName); + List users = keycloak.realm(REALM_NAME) + .roles() + .get(roleName) + .getUserMembers(); + logger.info("Users found by role {}", users.stream() + .map(user -> user.getUsername()) + .collect(Collectors.toList())); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/App.java b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/App.java new file mode 100644 index 0000000000..a863bde2e3 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/adminclient/App.java @@ -0,0 +1,30 @@ +package com.baeldung.keycloak.adminclient; + +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.PropertySource; + +@SpringBootApplication(scanBasePackages = { "com.baeldung.keycloak.adminclient" }) +@PropertySource("classpath:application-adminclient.properties") +public class App { + + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } + + @Bean + Keycloak keycloak() { + return KeycloakBuilder.builder() + .serverUrl("http://localhost:8080") + .realm("master") + .clientId("admin-cli") + .grantType(OAuth2Constants.PASSWORD) + .username("admin") + .password("password") + .build(); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProvider.java b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProvider.java new file mode 100644 index 0000000000..c34514104b --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProvider.java @@ -0,0 +1,52 @@ +package com.baeldung.keycloak.customendpoint; + +import java.util.Optional; +import java.util.stream.Stream; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.services.resource.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 searchUsersByGroupAndRoleName(@QueryParam("groupName") @NotNull String groupName, @QueryParam("roleName") @NotBlank String roleName) { + RealmModel realm = session.getContext().getRealm(); + + Optional 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)); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProviderFactory.java b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProviderFactory.java new file mode 100644 index 0000000000..34eda94d6a --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-2/src/main/java/com/baeldung/keycloak/customendpoint/KeycloakUserApiProviderFactory.java @@ -0,0 +1,33 @@ +package com.baeldung.keycloak.customendpoint; + +import org.keycloak.Config.Scope; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.services.resource.RealmResourceProvider; +import org.keycloak.services.resource.RealmResourceProviderFactory; + +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; + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak-2/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory b/spring-boot-modules/spring-boot-keycloak-2/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory new file mode 100644 index 0000000000..f11a60fd2b --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-2/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory @@ -0,0 +1 @@ +com.baeldung.keycloak.customendpoint.KeycloakUserApiProviderFactory \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak-2/src/main/resources/application-adminclient.properties b/spring-boot-modules/spring-boot-keycloak-2/src/main/resources/application-adminclient.properties new file mode 100644 index 0000000000..bafddced85 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-2/src/main/resources/application-adminclient.properties @@ -0,0 +1 @@ +server.port=8081 \ No newline at end of file