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

摘要: 原创出处 http://www.iocoder.cn/Apollo/openapi-auth-2/ 「芋道源码」欢迎转载,保留摘要,谢谢!


1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Apollo 开放平台》

本文接 《Apollo 源码解析 —— OpenAPI 认证与授权(一)之认证》侧重在授权部分。和 Portal 的授权一样:

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

2. 权限模型

和 Portal 使用相同的权限模型,差别在于 UserRole 换成了 ConsumerRole 。所以,关系如下图:关系

2.1 ConsumerRole

ConsumerRole 表,Consumer 与角色的关联表,对应实体 com.ctrip.framework.apollo.openapi.entity.ConsumerRole ,代码如下:

@Entity
@Table(name = "ConsumerRole")
@SQLDelete(sql = "Update ConsumerRole set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class ConsumerRole extends BaseEntity {

    /**
     * Consumer 编号 {@link Consumer#id}
     */
    @Column(name = "ConsumerId", nullable = false)
    private long consumerId;
    /**
     * Role 编号 {@link com.ctrip.framework.apollo.portal.entity.po.Role#id}
     */
    @Column(name = "RoleId", nullable = false)
    private long roleId;
}
  • 字段比较简单,胖友自己看注释。

3. ConsumerService

com.ctrip.framework.apollo.openapi.service.ConsumerService ,提供 Consumer、ConsumerToken、ConsumerAudit、ConsumerRole 相关的 Service 逻辑。

3.1 createConsumerRole

#createConsumerRole(consumerId, roleId, operator) 方法,创建 Consumer 对象。代码如下:

ConsumerRole createConsumerRole(Long consumerId, Long roleId, String operator) {
    ConsumerRole consumerRole = new ConsumerRole();
    consumerRole.setConsumerId(consumerId);
    consumerRole.setRoleId(roleId);
    consumerRole.setDataChangeCreatedBy(operator);
    consumerRole.setDataChangeLastModifiedBy(operator);
    return consumerRole;
}

3.2 assignAppRoleToConsumer

#assignAppRoleToConsumer(token, appId) 方法,授权 App 的 Role 给 Consumer 。代码如下:

@Transactional
public ConsumerRole assignAppRoleToConsumer(String token, String appId) {
    // 校验 Token 是否有对应的 Consumer 。若不存在,抛出 BadRequestException 异常
    Long consumerId = getConsumerIdByToken(token);
    if (consumerId == null) {
        throw new BadRequestException("Token is Illegal");
    }

    // 获得 App 对应的 Role 对象
    Role masterRole = rolePermissionService.findRoleByRoleName(RoleUtils.buildAppMasterRoleName(appId));
    if (masterRole == null) {
        throw new BadRequestException("App's role does not exist. Please check whether app has created.");
    }

    // 获得 Consumer 对应的 ConsumerRole 对象。若已存在,返回 ConsumerRole 对象
    long roleId = masterRole.getId();
    ConsumerRole managedModifyRole = consumerRoleRepository.findByConsumerIdAndRoleId(consumerId, roleId);
    if (managedModifyRole != null) {
        return managedModifyRole;
    }

    // 创建 Consumer 对应的 ConsumerRole 对象
    String operator = userInfoHolder.getUser().getUserId();
    ConsumerRole consumerRole = createConsumerRole(consumerId, roleId, operator);
    // 保存 Consumer 对应的 ConsumerRole 对象
    return consumerRoleRepository.save(consumerRole);
}

3.3 assignNamespaceRoleToConsumer

#assignNamespaceRoleToConsumer(token, appId, namespaceName) 方法,授权 Namespace 的 Role 给 Consumer 。对吗如下:

@Transactional
public List<ConsumerRole> assignNamespaceRoleToConsumer(String token, String appId, String namespaceName) {
    // 校验 Token 是否有对应的 Consumer 。若不存在,抛出 BadRequestException 异常
    Long consumerId = getConsumerIdByToken(token);
    if (consumerId == null) {
        throw new BadRequestException("Token is Illegal");
    }

    // 获得 Namespace 对应的 Role 们。若有任一不存在,抛出 BadRequestException 异常
    Role namespaceModifyRole = rolePermissionService.findRoleByRoleName(RoleUtils.buildModifyNamespaceRoleName(appId, namespaceName));
    Role namespaceReleaseRole = rolePermissionService.findRoleByRoleName(RoleUtils.buildReleaseNamespaceRoleName(appId, namespaceName));
    if (namespaceModifyRole == null || namespaceReleaseRole == null) {
        throw new BadRequestException("Namespace's role does not exist. Please check whether namespace has created.");
    }
    long namespaceModifyRoleId = namespaceModifyRole.getId();
    long namespaceReleaseRoleId = namespaceReleaseRole.getId();

    // 获得 Consumer 对应的 ConsumerRole 们。若都存在,返回 ConsumerRole 数组
    ConsumerRole managedModifyRole = consumerRoleRepository.findByConsumerIdAndRoleId(consumerId, namespaceModifyRoleId);
    ConsumerRole managedReleaseRole = consumerRoleRepository.findByConsumerIdAndRoleId(consumerId, namespaceReleaseRoleId);
    if (managedModifyRole != null && managedReleaseRole != null) {
        return Arrays.asList(managedModifyRole, managedReleaseRole);
    }

    // 创建 Consumer 对应的 ConsumerRole 们
    String operator = userInfoHolder.getUser().getUserId();
    ConsumerRole namespaceModifyConsumerRole = createConsumerRole(consumerId, namespaceModifyRoleId, operator);
    ConsumerRole namespaceReleaseConsumerRole = createConsumerRole(consumerId, namespaceReleaseRoleId, operator);
    // 保存 Consumer 对应的 ConsumerRole 们到数据库中
    ConsumerRole createdModifyConsumerRole = consumerRoleRepository.save(namespaceModifyConsumerRole);
    ConsumerRole createdReleaseConsumerRole = consumerRoleRepository.save(namespaceReleaseConsumerRole);
    // 返回 ConsumerRole 数组
    return Arrays.asList(createdModifyConsumerRole, createdReleaseConsumerRole);
}

4. ConsumerRolePermissionService

com.ctrip.framework.apollo.openapi.service.ConsumerRolePermissionService ,ConsumerRole 权限校验 Service 。代码如下:

@Service
public class ConsumerRolePermissionService {

    @Autowired
    private PermissionRepository permissionRepository;
    @Autowired
    private ConsumerRoleRepository consumerRoleRepository;
    @Autowired
    private RolePermissionRepository rolePermissionRepository;

    /**
     * Check whether user has the permission
     */
    public boolean consumerHasPermission(long consumerId, String permissionType, String targetId) {
        // 获得 Permission 对象
        Permission permission = permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
        // 若 Permission 不存在,返回 false
        if (permission == null) {
            return false;
        }

        // 获得 ConsumerRole 数组
        List<ConsumerRole> consumerRoles = consumerRoleRepository.findByConsumerId(consumerId);
        // 若数组为空,返回 false
        if (CollectionUtils.isEmpty(consumerRoles)) {
            return false;
        }

        // 获得 RolePermission 数组
        Set<Long> roleIds = consumerRoles.stream().map(ConsumerRole::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;
    }

}
  • DefaultRolePermissionService#userHasPermission(userId, permissionType, targetId) 方法,基本类似

5. ConsumerPermissionValidator

ConsumerPermissionValidator 和 PermissionValidator 基本类似

com.ctrip.framework.apollo.openapi.auth.ConsumerPermissionValidator ,Consumer 权限校验器。代码如下:

@Component
public class ConsumerPermissionValidator {

    @Autowired
    private ConsumerRolePermissionService permissionService;
    @Autowired
    private ConsumerAuthUtil consumerAuthUtil;

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

    public boolean hasModifyNamespacePermission(HttpServletRequest request, String appId, String namespaceName) {
        if (hasCreateNamespacePermission(request, appId)) {
            return true;
        }
        return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
                PermissionType.MODIFY_NAMESPACE,
                RoleUtils.buildNamespaceTargetId(appId, namespaceName));

    }

    public boolean hasReleaseNamespacePermission(HttpServletRequest request, String appId, String namespaceName) {
        if (hasCreateNamespacePermission(request, appId)) {
            return true;
        }
        return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
                PermissionType.RELEASE_NAMESPACE,
                RoleUtils.buildNamespaceTargetId(appId, namespaceName));

    }

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

    public boolean hasCreateNamespacePermission(HttpServletRequest request, String appId) {
        return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
                PermissionType.CREATE_NAMESPACE,
                appId);
    }

}

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

  • 创建 Namespace 的方法,添加了 @PreAuthorize(value = "@consumerPermissionValidator.hasCreateNamespacePermission(#request, #appId)")
  • 发布 Namespace 的方法,添加了 @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName)")

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

6. ConsumerController

apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.ConsumerController ,提供 Consumer、ConsumerToken、ConsumerAudit 相关的 API

创建第三方应用的界面中,点击【提交】按钮,调用授权 Consumer 的 API

创建第三方应用

代码如下:

@PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
@RequestMapping(value = "/consumers/{token}/assign-role", method = RequestMethod.POST)
public List<ConsumerRole> assignNamespaceRoleToConsumer(@PathVariable String token, @RequestParam String type, @RequestBody NamespaceDTO namespace) {
    String appId = namespace.getAppId();
    String namespaceName = namespace.getNamespaceName();
    // 校验 appId 非空。若为空,抛出 BadRequestException 异常
    if (StringUtils.isEmpty(appId)) {
        throw new BadRequestException("Params(AppId) can not be empty.");
    }

    // 授权 App 的 Role 给 Consumer
    if (Objects.equals("AppRole", type)) {
        return Collections.singletonList(consumerService.assignAppRoleToConsumer(token, appId));
    // 授权 Namespace 的 Role 给 Consumer
    } else {
        if (StringUtils.isEmpty(namespaceName)) {
            throw new BadRequestException("Params(NamespaceName) can not be empty.");
        }
        return consumerService.assignNamespaceRoleToConsumer(token, appId, namespaceName);
    }
}

666. 彩蛋

OpenAPI 在 v1/controller 中,实现了自己的 API ,共享调用 Portal 中的 Service 。如下图所示:OpenAPI Controller

  • 🙂 具体的 Controller 实现,胖友自己查看噢,笔者就不分享啦。
赞(0) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » Apollo 源码解析 —— OpenAPI 认证与授权(二)之授权
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

Java 技术驿站 | 致力打造 Java 精品博客

联系作者优质文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏