Răsfoiți Sursa

feat:组织与账号管理keycloak能力迁移

wangbo 2 ani în urmă
părinte
comite
c7932abc68

+ 4 - 0
smsb-customer-manager-adapter/pom.xml

@@ -38,6 +38,10 @@
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 90 - 4
smsb-customer-manager-adapter/src/main/java/com/inspur/customer/web/controller/keyclaok/KeycloakController.java

@@ -5,15 +5,27 @@ import com.alibaba.cola.dto.SingleResponse;
 import com.alibaba.fastjson.JSONObject;
 import com.inspur.customer.client.keycloak.KeycloakService;
 import com.inspur.customer.client.wechat.IWeChatService;
+import com.inspur.customer.constant.Constant;
+import com.inspur.customer.object.keycloak.SwitchDTO;
+import com.inspur.customer.object.keycloak.UsersRoleMappingDTO;
 import com.inspur.customer.object.wechat.SubscribeDto;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.dubbo.config.Constants;
 import org.apache.dubbo.config.annotation.DubboReference;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+import java.util.Objects;
+
 /**
  * 微信模块
  * @author wangbo13
@@ -21,7 +33,6 @@ import org.springframework.web.bind.annotation.*;
 @Slf4j
 @RestController
 @EnableScheduling
-@RequestMapping("/keycloak")
 public class KeycloakController {
 
     @Value("${system.appKey}")
@@ -33,7 +44,7 @@ public class KeycloakController {
     @DubboReference
     private IWeChatService iWeChatService;
 
-    @PostMapping("update/user")
+    @PostMapping("/keycloak/update/user")
     public Response updateUser(@RequestBody String json){
         SubscribeDto subscribe = JSONObject.parseObject(json, SubscribeDto.class);
         if(StringUtils.isEmpty(subscribe.getUserId())){
@@ -43,13 +54,13 @@ public class KeycloakController {
         return Response.buildSuccess();
     }
 
-    @GetMapping("query/ticket/{userId}")
+    @GetMapping("/keycloak/query/ticket/{userId}")
     public SingleResponse getTicket(@PathVariable("userId") String userId,
                                     @RequestHeader("Authorization") String authorization) {
         return SingleResponse.of(iWeChatService.queryTicket(appKey, userId, authorization));
     }
 
-    @PutMapping("changePassword")
+    @PutMapping("/keycloak/changePassword")
     public Response changePassword(@RequestHeader("userId") String userId, String newPassword) {
         if (StringUtils.isEmpty(userId)) {
             return Response.buildFailure("400", "密码不能为空");
@@ -70,4 +81,79 @@ public class KeycloakController {
             }
         }
     }
+
+    @PostMapping("/admin/group/{id}/children")
+    public Response addKeycloakGroup(@PathVariable("id") String id , @RequestBody GroupRepresentation groupRepresentation){
+        if(StringUtils.isEmpty(groupRepresentation.getName())){
+            return Response.buildFailure("500","name不能为空!");
+        }
+        if(CollectionUtils.isEmpty(groupRepresentation.getAttributes())){
+            return Response.buildFailure("500","attributes不能为空!");
+        }
+        return keycloakService.addKeycloakGroup(id , groupRepresentation);
+    }
+
+    @PutMapping("/admin/group/{id}")
+    public Response updateKeycloakGroup(@PathVariable("id") String id , @RequestBody GroupRepresentation groupRepresentation){
+        if(StringUtils.isEmpty(groupRepresentation.getName())){
+            return Response.buildFailure("500","name不能为空!");
+        }
+        if(CollectionUtils.isEmpty(groupRepresentation.getAttributes())){
+            return Response.buildFailure("500","attributes不能为空!");
+        }
+        return keycloakService.updateKeycloakGroup(id , groupRepresentation);
+    }
+
+    @DeleteMapping("/admin/group/{id}")
+    public Response removeGroup(@PathVariable("id") String id){
+        return keycloakService.removeGroup(id);
+    }
+
+    @PostMapping("/admin/users/add")
+    public Response addKeyClaokUser(@RequestBody UserRepresentation userRepresentation){
+        if(StringUtils.isEmpty(userRepresentation.getUsername())){
+            return Response.buildFailure("500","userName不能为空!");
+        }
+        if(CollectionUtils.isEmpty(userRepresentation.getGroups())){
+            return Response.buildFailure("500","groups不能为空!");
+        }
+        if(CollectionUtils.isEmpty(userRepresentation.getCredentials())){
+            return Response.buildFailure("500","credentials不能为空!");
+        }
+        return keycloakService.addKeyClaokUser(userRepresentation);
+    }
+
+    @PutMapping("/admin/users/{userId}")
+    public Response enableSwitch(@PathVariable("userId") String userId, @RequestBody SwitchDTO switchDTO){
+        log.info("userId:{}",userId);
+        return keycloakService.updateUser(userId , switchDTO , Constant.ENABLE);
+    }
+
+    @PutMapping("/admin/users/{userId}/reset")
+    public Response resetPassword(@PathVariable("userId") String userId , @RequestBody CredentialRepresentation credential){
+        if(StringUtils.isEmpty(credential.getType()) || !credential.getType().equals(Constant.type)){
+            return Response.buildFailure("500","type参数不合法!");
+        }
+        if(StringUtils.isEmpty(credential.getValue())){
+            return Response.buildFailure("500","value不能为空!");
+        }
+        return keycloakService.updateUser(userId , credential , Constant.RESET_PASSWORD);
+    }
+
+    @DeleteMapping("/admin/users/{userId}")
+    public Response enableSwitch(@PathVariable("userId") String userId){
+        return keycloakService.removeUser(userId);
+    }
+
+    @PutMapping("/admin/users/{userId}/groups/{groupId}")
+    public Response regrouping(@PathVariable("userId")String userId,
+                               @PathVariable("groupId")String groupId){
+        return keycloakService.regrouping(userId , groupId);
+    }
+
+    @PutMapping("/admin/users/role/configure")
+    public Response roleMapping(@RequestBody UsersRoleMappingDTO ssersRoleMappingDTO){
+        return keycloakService.roleMapping(ssersRoleMappingDTO);
+    }
+
 }

+ 90 - 3
smsb-customer-manager-app/src/main/java/com/inspur/customer/service/keycloak/KeycloakServiceImpl.java

@@ -1,14 +1,19 @@
 package com.inspur.customer.service.keycloak;
 
+import com.alibaba.cola.dto.Response;
+import com.alibaba.cola.dto.SingleResponse;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.inspur.customer.client.keycloak.KeycloakService;
 import com.inspur.customer.object.keycloak.KeycloakUserCO;
+import com.inspur.customer.object.keycloak.SwitchDTO;
+import com.inspur.customer.object.keycloak.UsersRoleMappingDTO;
 import com.inspur.customer.object.wechat.Pair;
+import com.inspur.customer.constant.Constant;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboService;
-import org.keycloak.admin.client.resource.RealmResource;
-import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.*;
 import org.keycloak.representations.idm.*;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
@@ -17,6 +22,7 @@ import javax.annotation.Resource;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @Slf4j
 @DubboService
@@ -240,4 +246,85 @@ public class KeycloakServiceImpl implements KeycloakService {
         });
         return userMap;
     }
+
+    @Override
+    public Response addKeycloakGroup(String id , GroupRepresentation groupRepresentation) {
+        return SingleResponse.of(realmResource.groups().group(id).subGroup(groupRepresentation).getDate());
+    }
+
+    @Override
+    public Response updateKeycloakGroup(String id, GroupRepresentation groupRepresentation) {
+        realmResource.groups().group(id).update(groupRepresentation);
+        return SingleResponse.buildSuccess();
+    }
+
+    @Override
+    public Response removeGroup(String id) {
+        realmResource.groups().group(id).remove();
+        return SingleResponse.buildSuccess();
+    }
+
+    @Override
+    public Response addKeyClaokUser(UserRepresentation userRepresentation) {
+        return SingleResponse.of(realmResource.users().create(userRepresentation).getDate());
+    }
+
+    @Override
+    public Response updateUser(String userId, Object representation ,Integer oprateType) {
+        UserResource userResource = realmResource.users().get(userId);
+        UserRepresentation user = userResource.toRepresentation();
+        switch (oprateType){
+            case Constant.ENABLE:
+                SwitchDTO switchDTO = (SwitchDTO) representation;
+                user.setEnabled(switchDTO.getEnabled());
+                break;
+            case Constant.RESET_PASSWORD:
+                CredentialRepresentation credential = (CredentialRepresentation) representation;
+                user.setCredentials(Stream.of(credential).collect(Collectors.toList()));
+                break;
+            default:
+                break;
+        }
+        userResource.update(user);
+        return SingleResponse.buildSuccess();
+    }
+
+    @Override
+    public Response removeUser(String userId) {
+        realmResource.users().get(userId).remove();
+        return SingleResponse.buildSuccess();
+    }
+
+    @Override
+    public Response regrouping(String userId, String groupId) {
+        UserResource userResource = realmResource.users().get(userId);
+        List<GroupRepresentation> groups = userResource.groups();
+        // 移除
+        groups.forEach(groupRepresentation->{userResource.leaveGroup(groupRepresentation.getId());});
+        // 添加
+        GroupsResource initGroups = realmResource.groups();
+        List<String> targetGroups = Arrays.asList(initGroups.group(groupId).toRepresentation().getPath().split("/"))
+            .stream().filter(t -> Objects.nonNull(t)).collect(Collectors.toList());
+
+        getGroupId(targetGroups ,initGroups.groups()).forEach(targetGroupId->{userResource.joinGroup(targetGroupId);});
+        return SingleResponse.buildSuccess();
+    }
+
+    private List<String>  getGroupId(List<String> targetGroups ,  List<GroupRepresentation> groups){
+        List<String> groupIds = Lists.newArrayList();
+        GroupRepresentation group = groups.stream().filter(ele -> targetGroups.contains(ele.getName())).findFirst().get();
+        groupIds.add(group.getId());
+        if(!CollectionUtils.isEmpty(group.getSubGroups())){
+            groupIds.addAll(getGroupId(targetGroups , group.getSubGroups()));
+        }
+        return groupIds;
+    }
+
+    @Override
+    public Response roleMapping(UsersRoleMappingDTO ssersRoleMappingDTO) {
+        RoleScopeResource roleScopeResource = realmResource.users().get(ssersRoleMappingDTO.getUserId()).roles().realmLevel();
+        roleScopeResource.add(Optional.ofNullable(ssersRoleMappingDTO.getAddRoleList()).orElse(Lists.newArrayList()));
+        roleScopeResource.remove(Optional.ofNullable(ssersRoleMappingDTO.getRemoveRoleList()).orElse(Lists.newArrayList()));
+        return SingleResponse.buildSuccess();
+    }
 }

+ 4 - 0
smsb-customer-manager-client/pom.xml

@@ -62,6 +62,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-json</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 76 - 0
smsb-customer-manager-client/src/main/java/com/inspur/customer/client/keycloak/KeycloakService.java

@@ -1,6 +1,13 @@
 package com.inspur.customer.client.keycloak;
+import com.alibaba.cola.dto.Response;
 import com.inspur.customer.object.keycloak.KeycloakUserCO;
+import com.inspur.customer.object.keycloak.UsersRoleMappingDTO;
 import com.inspur.customer.object.wechat.Pair;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.springframework.web.bind.annotation.RequestBody;
 
 import java.util.List;
 import java.util.Map;
@@ -79,4 +86,73 @@ public interface KeycloakService {
      * @return
      */
     Map<String, String> getUsersMap();
+
+    /**
+     * 该租户下添加部门
+     *
+     * @param id
+     * @param groupRepresentation
+     * @return
+     */
+    Response addKeycloakGroup(String id , GroupRepresentation groupRepresentation);
+
+    /**
+     * 该租户下修改部门
+     *
+     * @param id
+     * @param groupRepresentation
+     * @return
+     */
+    Response updateKeycloakGroup(String id , GroupRepresentation groupRepresentation);
+
+    /**
+     * 该租户下删除部门
+     *
+     * @param id
+     * @return
+     */
+    Response removeGroup(String id);
+
+    /**
+     * 添加用户到某部门
+     *
+     * @param userRepresentation
+     * @return
+     */
+    Response addKeyClaokUser(UserRepresentation userRepresentation);
+
+    /**
+     * 更新用户信息
+     *
+     * @param userId
+     * @param representation
+     * @param oprateType 1:账号启用/禁用 2:重置密码
+     * @return
+     */
+    Response updateUser(String userId , Object representation ,Integer oprateType);
+
+    /**
+     * 注销用户
+     *
+     * @param userId
+     * @return
+     */
+    Response removeUser(String userId);
+
+    /**
+     * 用户重分组
+     *
+     * @param userId
+     * @param groupId
+     * @return
+     */
+    Response regrouping(String userId , String groupId);
+
+    /**
+     * 用户角色分配
+     *
+     * @param ssersRoleMappingDTO
+     * @return
+     */
+    Response roleMapping(UsersRoleMappingDTO ssersRoleMappingDTO);
 }

+ 25 - 0
smsb-customer-manager-client/src/main/java/com/inspur/customer/constant/Constant.java

@@ -0,0 +1,25 @@
+package com.inspur.customer.constant;
+
+/**
+ * @Author wangbo13
+ * @Date 2022/11/22 16:35
+ * @Version 1.0
+ */
+public final class Constant {
+
+    /**
+     * 账号启用/禁用
+     */
+    public static final int ENABLE = 1;
+
+    /**
+     * 重置密码
+     */
+    public static final int RESET_PASSWORD = 2;
+
+    /**
+     * 凭证类型
+     */
+    public static final String type = "password";
+
+}

+ 22 - 0
smsb-customer-manager-client/src/main/java/com/inspur/customer/object/keycloak/SwitchDTO.java

@@ -0,0 +1,22 @@
+package com.inspur.customer.object.keycloak;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * @Author wangbo13
+ * @Date 2022/11/22 15:29
+ * @Version 1.0
+ */
+@Data
+public class SwitchDTO implements Serializable {
+    private static final long serialVersionUID = 3364299451602207573L;
+
+    /**
+     * 启用/禁用 true/false
+     */
+    @NotNull(message = "enabled不能为空")
+    private Boolean enabled;
+}

+ 32 - 0
smsb-customer-manager-client/src/main/java/com/inspur/customer/object/keycloak/UsersRoleMappingDTO.java

@@ -0,0 +1,32 @@
+package com.inspur.customer.object.keycloak;
+
+import lombok.Data;
+import org.keycloak.representations.idm.RoleRepresentation;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Author wangbo13
+ * @Date 2022/11/24 9:24
+ * @Version 1.0
+ */
+@Data
+public class UsersRoleMappingDTO implements Serializable {
+    private static final long serialVersionUID = -8477367953520065512L;
+
+    /**
+     * 用户Id
+     */
+    private String userId;
+
+    /**
+     * 新增角色
+     */
+    private List<RoleRepresentation> addRoleList;
+
+    /**
+     * 删除角色
+     */
+    private List<RoleRepresentation> removeRoleList;
+}