前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Apollo 源码解析 —— Portal 认证与授权(二)之授权

Apollo 源码解析 —— Portal 认证与授权(二)之授权

作者头像
芋道源码
发布2020-06-04 11:04:58
1.4K0
发布2020-06-04 11:04:58
举报
文章被收录于专栏:芋道源码1024

1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Portal 实现用户登录功能》 。

本文接 《Apollo 源码解析 —— Portal 认证与授权(一)之认证》 ,侧重在授权部分。在上一文中,我们提到:

具体每个 URL 的权限校验,通过在对应的方法上,添加 @PreAuthorize 方法注解,配合具体的方法参数,一起校验功能 + 数据级的权限校验。

2. 权限模型

常见的权限模型,有两种:RBAC 和 ACL 。如果不了解的胖友,可以看下 《基于AOP实现权限管理:访问控制模型 RBAC 和 ACL 》 。

笔者一开始看到 Role + UserRole + Permission + RolePermission 四张表,认为是 RBAC 权限模型。但是看了 Permission 的数据结构,以及 PermissionValidator 的权限判断方式,又感受到几分 ACL 权限模型的味道。

所以,很难完全说,Apollo 属于 RBAC 还是 ACL 权限模型。或者说,权限模型,本身会根据实际业务场景的业务需要,做一些变种和改造。权限模型,提供给我们的是指导和借鉴,不需要过于拘泥。

关系如下图:

2.1 Role

Role 表,角色表,对应实体 com.ctrip.framework.apollo.portal.entity.po.Role ,代码如下:

代码语言:javascript
复制
@Entity
@Table(name = "Role")
@SQLDelete(sql = "Update Role set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Role extends BaseEntity {

    /**
     * 角色名
     */
    @Column(name = "RoleName", nullable = false)
    private String roleName;

}
  • roleName 字段,角色名,通过系统自动生成。目前有三种类型( 不是三个 )角色:
    • App 管理员,格式为 "Master + AppId" ,例如:"Master+100004458"
    • Namespace 修改管理员,格式为 "ModifyNamespace + AppId + NamespaceName" ,例如:"ModifyNamespace+100004458+application"
    • Namespace 发布管理员,格式为 "ReleaseNamespace + AppId + NamespaceName" ,例如:"ReleaseNamespace+100004458+application"
  • 例子如下图:

2.2 UserRole

UserRole 表,用户与角色的关联表,对应实体 com.ctrip.framework.apollo.portal.entity.po.UserRole ,代码如下:

代码语言:javascript
复制
@Entity
@Table(name = "UserRole")
@SQLDelete(sql = "Update UserRole set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class UserRole extends BaseEntity {

    /**
     * 账号 {@link UserPO#username}
     */
    @Column(name = "UserId", nullable = false)
    private String userId;
    /**
     * 角色编号 {@link Role#id}
     */
    @Column(name = "RoleId", nullable = false)
    private long roleId;

}
  • userId 字段,用户编号,指向对应的 User 。目前使用 UserPO.username 。当然,我们自己的业务系统里,推荐使用 UserPO.id
  • roleId 字段,角色编号,指向对应的 Role 。
  • 例子如下图:

2.3 Permission

Permission 表,权限表,对应实体 com.ctrip.framework.apollo.portal.entity.po.Permission ,代码如下:

代码语言:javascript
复制
@Entity
@Table(name = "Permission")
@SQLDelete(sql = "Update Permission set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Permission extends BaseEntity {

    /**
     * 权限类型
     */
    @Column(name = "PermissionType", nullable = false)
    private String permissionType;
    /**
     * 目标编号
     */
    @Column(name = "TargetId", nullable = false)
    private String targetId;
    
}
  • permissionType 字段,权限类型。在 com.ctrip.framework.apollo.portal.constant.PermissionType 中枚举,代码如下: public interface PermissionType { // ========== APP level permission ========== String CREATE_NAMESPACE = "CreateNamespace"; // 创建 Namespace String CREATE_CLUSTER = "CreateCluster"; // 创建 Cluster String ASSIGN_ROLE = "AssignRole"; // 分配用户权限的权限 // ========== namespace level permission ========= String MODIFY_NAMESPACE = "ModifyNamespace"; // 修改 Namespace String RELEASE_NAMESPACE = "ReleaseNamespace"; // 发布 Namespace }
    • 分成 App 和 Namespace 两种级别的权限类型。
  • targetId 字段,目标编号。
  • 例子如下图:
  • 为什么不是 Namespace 的编号?Namespace 级别,是所有环境 + 所有集群都有权限,所以不能具体某个 Namespace 。
  • App 级别时,targetId 指向 "App 编号"。
  • Namespace 级别时,targetId 指向 "App 编号 + Namespace 名字"。

2.4 RolePermission

RolePermission 表,角色与权限的关联表,对应实体 com.ctrip.framework.apollo.portal.entity.po.RolePermission ,代码如下:

代码语言:javascript
复制
@Entity
@Table(name = "RolePermission")
@SQLDelete(sql = "Update RolePermission set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class RolePermission extends BaseEntity {

    /**
     * 角色编号 {@link Role#id}
     */
    @Column(name = "RoleId", nullable = false)
    private long roleId;
    /**
     * 权限编号 {@link Permission#id}
     */
    @Column(name = "PermissionId", nullable = false)
    private long permissionId;

}
  • roleId 字段,角色编号,指向对应的 Role 。
  • permissionId 字段,权限编号,指向对应的 Permission 。
  • 例子如下图:

3. RolePermissionService

com.ctrip.framework.apollo.portal.service.RolePermissionService ,提供 Role、UserRole、Permission、UserPermission 相关的操作。代码如下:

代码语言:javascript
复制
public interface RolePermissionService {

    // ========== Role 相关 ==========
    /**
     * Create role with permissions, note that role name should be unique
     */
    Role createRoleWithPermissions(Role role, Set<Long> permissionIds);
    /**
     * Find role by role name, note that roleName should be unique
     */
    Role findRoleByRoleName(String roleName);

    // ========== UserRole 相关 ==========
    /**
     * Assign role to users
     *
     * @return the users assigned roles
     */
    Set<String> assignRoleToUsers(String roleName, Set<String> userIds, String operatorUserId);
    /**
     * Remove role from users
     */
    void removeRoleFromUsers(String roleName, Set<String> userIds, String operatorUserId);
    /**
     * Query users with role
     */
    Set<UserInfo> queryUsersWithRole(String roleName);

    // ========== UserPermission 相关 ==========
    /**
     * Check whether user has the permission
     */
    boolean userHasPermission(String userId, String permissionType, String targetId);
    /**
     * 校验是否为超级管理员
     */
    boolean isSuperAdmin(String userId);

    // ========== Permission 相关 ==========
    /**
     * Create permission, note that permissionType + targetId should be unique
     */
    Permission createPermission(Permission permission);
    /**
     * Create permissions, note that permissionType + targetId should be unique
     */
    Set<Permission> createPermissions(Set<Permission> permissions);

}

3.1 DefaultRolePermissionService

com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultRolePermissionService ,实现 RolePermissionService 接口,默认 RolePermissionService 实现类。

老艿艿:下面的方法比较易懂,胖友看着代码注释理解。

3.1.1 createRoleWithPermissions

代码语言:javascript
复制
@Override
@Transactional
public Role createRoleWithPermissions(Role role, Set<Long> permissionIds) {
    // 获得 Role 对象,校验 Role 不存在
    Role current = findRoleByRoleName(role.getRoleName());
    Preconditions.checkState(current == null, "Role %s already exists!", role.getRoleName());

    // 新增 Role
    Role createdRole = roleRepository.save(role);

    // 授权给 Role
    if (!CollectionUtils.isEmpty(permissionIds)) {
        // 创建 RolePermission 数组
        Iterable<RolePermission> rolePermissions = permissionIds.stream().map(permissionId -> {
            RolePermission rolePermission = new RolePermission();
            rolePermission.setRoleId(createdRole.getId()); // Role 编号
            rolePermission.setPermissionId(permissionId);
            rolePermission.setDataChangeCreatedBy(createdRole.getDataChangeCreatedBy());
            rolePermission.setDataChangeLastModifiedBy(createdRole.getDataChangeLastModifiedBy());
            return rolePermission;
        }).collect(Collectors.toList());
        // 保存 RolePermission 数组
        rolePermissionRepository.save(rolePermissions);
    }

    return createdRole;
}

3.1.2 assignRoleToUsers

代码语言:javascript
复制
@Override
@Transactional
public Set<String> assignRoleToUsers(String roleName, Set<String> userIds, String operatorUserId) {
    // 获得 Role 对象,校验 Role 存在
    Role role = findRoleByRoleName(roleName);
    Preconditions.checkState(role != null, "Role %s doesn't exist!", roleName);

    // 获得已存在的 UserRole 数组
    List<UserRole> existedUserRoles = userRoleRepository.findByUserIdInAndRoleId(userIds, role.getId());
    Set<String> existedUserIds = existedUserRoles.stream().map(UserRole::getUserId).collect(Collectors.toSet());
    // 排除已经存在的
    Set<String> toAssignUserIds = Sets.difference(userIds, existedUserIds);

    // 创建需要新增的 UserRole 数组
    Iterable<UserRole> toCreate = toAssignUserIds.stream().map(userId -> {
        UserRole userRole = new UserRole();
        userRole.setRoleId(role.getId());
        userRole.setUserId(userId);
        userRole.setDataChangeCreatedBy(operatorUserId);
        userRole.setDataChangeLastModifiedBy(operatorUserId);
        return userRole;
    }).collect(Collectors.toList());
    // 保存 RolePermission 数组
    userRoleRepository.save(toCreate);

    return toAssignUserIds;
}

3.1.3 removeRoleFromUsers

代码语言:javascript
复制
@Override
@Transactional
public void removeRoleFromUsers(String roleName, Set<String> userIds, String operatorUserId) {
    // 获得 Role 对象,校验 Role 存在
    Role role = findRoleByRoleName(roleName);
    Preconditions.checkState(role != null, "Role %s doesn't exist!", roleName);

    // 获得已存在的 UserRole 数组
    List<UserRole> existedUserRoles = userRoleRepository.findByUserIdInAndRoleId(userIds, role.getId());
    // 标记删除
    for (UserRole userRole : existedUserRoles) {
        userRole.setDeleted(true); // 标记删除
        userRole.setDataChangeLastModifiedTime(new Date());
        userRole.setDataChangeLastModifiedBy(operatorUserId);
    }

    // 保存 RolePermission 数组 【标记删除】
    userRoleRepository.save(existedUserRoles);
}

3.1.4 queryUsersWithRole

代码语言:javascript
复制
@Override
public Set<UserInfo> queryUsersWithRole(String roleName) {
    // 获得 Role 对象,校验 Role 存在
    Role role = findRoleByRoleName(roleName);

    // Role 不存在时,返回空数组
    if (role == null) {
        return Collections.emptySet();
    }

    // 获得 UserRole 数组
    List<UserRole> userRoles = userRoleRepository.findByRoleId(role.getId());
    // 转换成 UserInfo 数组
    return userRoles.stream().map(userRole -> {
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(userRole.getUserId());
        return userInfo;
    }).collect(Collectors.toSet());
}

3.1.5 findRoleByRoleName

代码语言:javascript
复制
public Role findRoleByRoleName(String roleName) {
    return roleRepository.findTopByRoleName(roleName);
}

3.1.6 userHasPermission 【重要】

代码语言:javascript
复制
@Override
public boolean userHasPermission(String userId, String permissionType, String targetId) {
    // 获得 Permission 对象
    Permission permission = permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
    // 若 Permission 不存在,返回 false
    if (permission == null) {
        return false;
    }

    // 若是超级管理员,返回 true 【有权限】
    if (isSuperAdmin(userId)) {
        return true;
    }

    // 获得 UserRole 数组
    List<UserRole> userRoles = userRoleRepository.findByUserId(userId);
    // 若数组为空,返回 false
    if (CollectionUtils.isEmpty(userRoles)) {
        return false;
    }

    // 获得 RolePermission 数组
    Set<Long> roleIds = userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toSet());
    List<RolePermission> rolePermissions = rolePermissionRepository.findByRoleIdIn(roleIds);
    // 若数组为空,返回 false
    if (CollectionUtils.isEmpty(rolePermissions)) {
        return false;
    }

    // 判断是否有对应的 RolePermission 。若有,则返回 true 【有权限】
    for (RolePermission rolePermission : rolePermissions) {
        if (rolePermission.getPermissionId() == permission.getId()) {
            return true;
        }
    }

    return false;
}
  • 从目前的代码看下来,这个权限判断的过程,是 ACL 的方式。
  • 如果是 RBAC 的方式,获得 Permission 后,再获得 Permission 对应的 RolePermission 数组,最后和 User 对应的 UserRole 数组,求 roleId 是否相交。

3.1.7 isSuperAdmin

代码语言:javascript
复制
@Override
public boolean isSuperAdmin(String userId) {
    return portalConfig.superAdmins().contains(userId);
}
  • 通过 ServerConfig 的 "superAdmin" 配置项,判断是否存在该账号。

3.1.8 createPermissions

代码语言:javascript
复制
@Override
@Transactional
public Permission createPermission(Permission permission) {
    String permissionType = permission.getPermissionType();
    String targetId = permission.getTargetId();
    // 获得 Permission 对象,校验 Permission 为空
    Permission current = permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
    Preconditions.checkState(current == null, "Permission with permissionType %s targetId %s already exists!", permissionType, targetId);

    // 保存 Permission
    return permissionRepository.save(permission);
}

3.1.8 createPermissions

代码语言:javascript
复制
@Override
@Transactional
public Set<Permission> createPermissions(Set<Permission> permissions) {
    // 创建 Multimap 对象,用于下面校验的分批的批量查询
    Multimap<String, String> targetIdPermissionTypes = HashMultimap.create();
    for (Permission permission : permissions) {
        targetIdPermissionTypes.put(permission.getTargetId(), permission.getPermissionType());
    }

    // 查询 Permission 集合,校验都不存在
    for (String targetId : targetIdPermissionTypes.keySet()) {
        Collection<String> permissionTypes = targetIdPermissionTypes.get(targetId);
        List<Permission> current = permissionRepository.findByPermissionTypeInAndTargetId(permissionTypes, targetId);
        Preconditions.checkState(CollectionUtils.isEmpty(current), "Permission with permissionType %s targetId %s already exists!", permissionTypes, targetId);
    }

    // 保存 Permission 集合
    Iterable<Permission> results = permissionRepository.save(permissions);
    // 转成 Permission 集合,返回
    return StreamSupport.stream(results.spliterator(), false).collect(Collectors.toSet());
}

4. RoleInitializationService

com.ctrip.framework.apollo.portal.service.RoleInitializationService ,提供角色初始化相关的操作。代码如下:

代码语言:javascript
复制
public interface RoleInitializationService {

    /**
     * 初始化 App 级的 Role
     */
    void initAppRoles(App app);

    /**
     * 初始化 Namespace 级的 Role
     */
    void initNamespaceRoles(String appId, String namespaceName, String operator);

}

4.1 DefaultRoleInitializationService

com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultRoleInitializationService ,实现 RoleInitializationService 接口,默认 RoleInitializationService 实现类。

4.1.1 initAppRoles

代码语言:javascript
复制
  1: @Override
  2: @Transactional
  3: public void initAppRoles(App app) {
  4:     String appId = app.getAppId();
  5: 
  6:     // 创建 App 拥有者的角色名
  7:     String appMasterRoleName = RoleUtils.buildAppMasterRoleName(appId);
  8:     // has created before
  9:     // 校验角色是否已经存在。若是,直接返回
 10:     if (rolePermissionService.findRoleByRoleName(appMasterRoleName) != null) {
 11:         return;
 12:     }
 13:     String operator = app.getDataChangeCreatedBy();
 14:     //create app permissions
 15:     // 创建 App 角色
 16:     createAppMasterRole(appId, operator);
 17:     // 授权 Role 给 App 拥有者
 18:     // assign master role to user
 19:     rolePermissionService.assignRoleToUsers(RoleUtils.buildAppMasterRoleName(appId), Sets.newHashSet(app.getOwnerName()), operator);
 20: 
 21:     // 初始化 Namespace 角色
 22:     initNamespaceRoles(appId, ConfigConsts.NAMESPACE_APPLICATION, operator);
 23:     // 授权 Role 给 App 创建者
 24:     //assign modify、release namespace role to user
 25:     rolePermissionService.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, ConfigConsts.NAMESPACE_APPLICATION, RoleType.MODIFY_NAMESPACE), Sets.newHashSet(operator), operator);
 26:     rolePermissionService.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, ConfigConsts.NAMESPACE_APPLICATION, RoleType.RELEASE_NAMESPACE), Sets.newHashSet(operator), operator);
 27: }
  • 在 Portal 创建完本地 App 后,自动初始化对应的 Role 们。调用如下图:
  • =========== 初始化 App 级的 Role ===========
  • 第 7 行:调用 RoleUtils#buildAppMasterRoleName(appId) 方法,创建 App 拥有者的角色名。代码如下: // RoleUtils.java private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR); public static String buildAppMasterRoleName(String appId) { return STRING_JOINER.join(RoleType.MASTER, appId); } // RoleType.java public static final String MASTER = "Master";
  • 第 9 至 12 行:调用 RolePermissionService#findRoleByRoleName(appMasterRoleName) 方法,校验角色是否已经存在。若是,直接返回。
  • 第 16 行:调用 #createAppMasterRole(appId, operator) 方法,创建 App 拥有者角色。代码如下: private void createAppMasterRole(String appId, String operator) { // 创建 App 对应的 Permission 集合,并保存到数据库 Set<Permission> appPermissions = Lists.newArrayList(PermissionType.CREATE_CLUSTER, PermissionType.CREATE_NAMESPACE, PermissionType.ASSIGN_ROLE) .stream().map(permissionType -> createPermission(appId, permissionType, operator) /* 创建 Permission 对象 */ ).collect(Collectors.toSet()); Set<Permission> createdAppPermissions = rolePermissionService.createPermissions(appPermissions); Set<Long> appPermissionIds = createdAppPermissions.stream().map(BaseEntity::getId).collect(Collectors.toSet()); // 创建 App 对应的 Role 对象,并保存到数据库 // create app master role Role appMasterRole = createRole(RoleUtils.buildAppMasterRoleName(appId), operator); rolePermissionService.createRoleWithPermissions(appMasterRole, appPermissionIds); }
    • x
    • x
    • 创建并保存 App 对应的 Permission 集合。#createPermission(targetId, permissionType, operator) 方法,创建 Permission 对象。代码如下: private Permission createPermission(String targetId, String permissionType, String operator) { Permission permission = new Permission(); permission.setPermissionType(permissionType); permission.setTargetId(targetId); permission.setDataChangeCreatedBy(operator); permission.setDataChangeLastModifiedBy(operator); return permission; }
    • 创建并保存 App 对应的 Role 对象,并授权对应的 Permission 集合。#createRole(roleName, operator) 方法,创建 Role 对象。代码如下: private Role createRole(String roleName, String operator) { Role role = new Role(); role.setRoleName(roleName); role.setDataChangeCreatedBy(operator); role.setDataChangeLastModifiedBy(operator); return role; }
  • 第 19 行:调用 rolePermissionService.assignRoleToUsers(roleName, userIds, operatorUserId) 方法,授权 Role 给 App 拥有者
  • =========== 初始化 Namespace 级的 Role ===========
  • 第 22 行:调用 #initNamespaceRoles(appId, namespaceName, operator) 方法,初始化 Namespace 的角色。详细解析,见 「4.2 initNamespaceRoles」 。
  • 第 23 至 26 行:调用 rolePermissionService.assignRoleToUsers(roleName, userIds, operatorUserId) 方法,授权 Role 给 App 创建者注意,此处不是“拥有者”噢。为什么?因为,Namespace 是自动创建的,并且是通过创建人来操作的。

4.1.2 initNamespaceRoles

代码语言:javascript
复制
@Override
@Transactional
public void initNamespaceRoles(String appId, String namespaceName, String operator) {
    // 创建 Namespace 修改的角色名
    String modifyNamespaceRoleName = RoleUtils.buildModifyNamespaceRoleName(appId, namespaceName);
    // 若不存在对应的 Role ,进行创建
    if (rolePermissionService.findRoleByRoleName(modifyNamespaceRoleName) == null) {
        createNamespaceRole(appId, namespaceName, PermissionType.MODIFY_NAMESPACE, RoleUtils.buildModifyNamespaceRoleName(appId, namespaceName), operator);
    }

    // 创建 Namespace 发布的角色名
    String releaseNamespaceRoleName = RoleUtils.buildReleaseNamespaceRoleName(appId, namespaceName);
    // 若不存在对应的 Role ,进行创建
    if (rolePermissionService.findRoleByRoleName(releaseNamespaceRoleName) == null) {
        createNamespaceRole(appId, namespaceName, PermissionType.RELEASE_NAMESPACE,
                RoleUtils.buildReleaseNamespaceRoleName(appId, namespaceName), operator);
    }
}
  • 在 Portal 创建完 Namespace 后,自动初始化对应的 Role 们。调用如下图:
  • 创建并保存 Namespace 修改发布对应的 Role 。
  • RoleUtils#buildModifyNamespaceRoleName(appId, namespaceName) 方法,创建 Namespace 修改的角色名。代码如下: // RoleUtils.java public static String buildModifyNamespaceRoleName(String appId, String namespaceName) { return STRING_JOINER.join(RoleType.MODIFY_NAMESPACE, appId, namespaceName); } // RoleType.java public static final String MODIFY_NAMESPACE = "ModifyNamespace";
  • RoleUtils#buildReleaseNamespaceRoleName(appId, namespaceName) 方法,创建 Namespace 发布的角色名。代码如下: // RoleUtils.java public static String buildReleaseNamespaceRoleName(String appId, String namespaceName) { return STRING_JOINER.join(RoleType.RELEASE_NAMESPACE, appId, namespaceName); } // RoleType.java public static final String RELEASE_NAMESPACE = "ReleaseNamespace";
  • #createNamespaceRole(...) 方法,创建 Namespace 的角色。代码如下: private void createNamespaceRole(String appId, String namespaceName, String permissionType, String roleName, String operator) { // 创建 Namespace 对应的 Permission 对象,并保存到数据库 Permission permission = createPermission(RoleUtils.buildNamespaceTargetId(appId, namespaceName), permissionType, operator); Permission createdPermission = rolePermissionService.createPermission(permission); // 创建 Namespace 对应的 Role 对象,并保存到数据库 Role role = createRole(roleName, operator); rolePermissionService.createRoleWithPermissions(role, Sets.newHashSet(createdPermission.getId())); }
    • x
    • 创建并保存 Namespace 对应的 Permission 对象。
    • 创建并保存 Namespace 对应的 Role 对象,并授权对应的 Permission 。
    • RoleUtils#buildNamespaceTargetId(appId, namespaceName) 方法,创建 Namespace 的目标编号。代码如下: public static String buildNamespaceTargetId(String appId, String namespaceName) { return STRING_JOINER.join(appId, namespaceName); }

5. PermissionValidator

com.ctrip.framework.apollo.portal.component.PermissionValidator ,权限校验器。代码如下:

代码语言:javascript
复制
@Component("permissionValidator")
public class PermissionValidator {

    @Autowired
    private UserInfoHolder userInfoHolder;
    @Autowired
    private RolePermissionService rolePermissionService;
    @Autowired
    private PortalConfig portalConfig;

    // ========== Namespace 级别 ==========

    public boolean hasModifyNamespacePermission(String appId, String namespaceName) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.MODIFY_NAMESPACE,
                RoleUtils.buildNamespaceTargetId(appId, namespaceName));
    }

    public boolean hasReleaseNamespacePermission(String appId, String namespaceName) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.RELEASE_NAMESPACE,
                RoleUtils.buildNamespaceTargetId(appId, namespaceName));
    }

    public boolean hasDeleteNamespacePermission(String appId) {
        return hasAssignRolePermission(appId) || isSuperAdmin();
    }

    public boolean hasOperateNamespacePermission(String appId, String namespaceName) {
        return hasModifyNamespacePermission(appId, namespaceName) || hasReleaseNamespacePermission(appId, namespaceName);
    }

    // ========== App 级别 ==========

    public boolean hasAssignRolePermission(String appId) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.ASSIGN_ROLE,
                appId);
    }

    public boolean hasCreateNamespacePermission(String appId) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.CREATE_NAMESPACE,
                appId);
    }

    public boolean hasCreateAppNamespacePermission(String appId, AppNamespace appNamespace) {
        boolean isPublicAppNamespace = appNamespace.isPublic();
        // 若满足如下任一条件:
        // 1. 公开类型的 AppNamespace 。
        // 2. 私有类型的 AppNamespace ,并且允许 App 管理员创建私有类型的 AppNamespace 。
        if (portalConfig.canAppAdminCreatePrivateNamespace() || isPublicAppNamespace) {
            return hasCreateNamespacePermission(appId);
        }
        // 超管
        return isSuperAdmin();
    }

    public boolean hasCreateClusterPermission(String appId) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.CREATE_CLUSTER,
                appId);
    }

    public boolean isAppAdmin(String appId) {
        return isSuperAdmin() || hasAssignRolePermission(appId);
    }

    // ========== 超管 级别 ==========

    public boolean isSuperAdmin() {
        return rolePermissionService.isSuperAdmin(userInfoHolder.getUser().getUserId());
    }

}

在每个需要校验权限的方法上,添加 @PreAuthorize 注解,并在 value 属性上写 EL 表达式,调用 PermissionValidator 的校验方法。例如:

  • 创建 Namespace 的方法,添加了 @PreAuthorize(value = "@permissionValidator.hasCreateNamespacePermission(#appId)")
  • 删除 Namespace 的方法,添加了 @PreAuthorize(value = "@permissionValidator.hasDeleteNamespacePermission(#appId)")

通过这样的方式,达到功能 + 数据级的权限控制。

6. PermissionController

com.ctrip.framework.apollo.portal.controller.PermissionController ,提供权限相关API 。如下图所示:

  • 每个方法,调用 RolePermissionService 的方法,提供 API 服务。
  • ? 代码比较简单,胖友自己查看。

对应界面为

  • App 级权限管理:
  • Namespace 级别权限管理:

666. 彩蛋

T T 老长一篇。哈哈哈,有种把所有代码 copy 过来的感觉。

突然发现没分享 com.ctrip.framework.apollo.portal.spi.configurationRoleConfigurationRole Spring Java 配置。代码如下:

代码语言:javascript
复制
@Configuration
public class RoleConfiguration {

    @Bean
    public RoleInitializationService roleInitializationService() {
        return new DefaultRoleInitializationService();
    }

    @Bean
    public RolePermissionService rolePermissionService() {
        return new DefaultRolePermissionService();
    }

}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 芋道源码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 概述
  • 2. 权限模型
    • 2.1 Role
      • 2.2 UserRole
        • 2.3 Permission
          • 2.4 RolePermission
          • 3. RolePermissionService
            • 3.1 DefaultRolePermissionService
              • 3.1.1 createRoleWithPermissions
              • 3.1.2 assignRoleToUsers
              • 3.1.3 removeRoleFromUsers
              • 3.1.4 queryUsersWithRole
              • 3.1.5 findRoleByRoleName
              • 3.1.6 userHasPermission 【重要】
              • 3.1.7 isSuperAdmin
              • 3.1.8 createPermissions
              • 3.1.8 createPermissions
          • 4. RoleInitializationService
            • 4.1 DefaultRoleInitializationService
              • 4.1.1 initAppRoles
              • 4.1.2 initNamespaceRoles
          • 5. PermissionValidator
          • 6. PermissionController
          • 666. 彩蛋
          相关产品与服务
          API 网关
          腾讯云 API 网关(API Gateway)是腾讯云推出的一种 API 托管服务,能提供 API 的完整生命周期管理,包括创建、维护、发布、运行、下线等。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档