写点什么

权限管理:RBAC 和 ACL 在 XIAOJUSURVEY 的应用

作者:XIAOJUSURVEY
  • 2024-07-31
    浙江
  • 本文字数:4182 字

    阅读完需:约 14 分钟

权限管理:RBAC和ACL在XIAOJUSURVEY的应用

引言

随着互联网技术的发展,问卷系统在各个领域得到了广泛应用。


我参与的XIAOJUSURVEY的开源问卷系统项目,是一个很不错的问卷开源项目,前后端都开源,该项目包含 B 端和 C 端,服务端采用 NestJS,前端采用 Vue3。


在项目的迭代过程中,我们引入了协作和空间功能,以提升用户体验和功能扩展性。


本文将详细介绍这些功能的设计与实现方案。

设计与实现

在设计问卷系统时,我们引入了空间概念,以更好地管理和组织问卷。空间功能分为个人空间和团队空间,两者在使用场景和权限管理上有显著区别。

定义

  • 个人空间:用于存储个人创建的问卷。每个用户在个人空间中独立工作,创建的问卷默认是私有的,但可以通过协作功能共享给其他用户。

  • 团队空间:允许用户创建和管理团队。每个团队空间可以包含多个成员,成员在团队空间中协作管理问卷。


企业级系统往往涉及复杂的组织管理和数据隐私安全,引入空间的概念是为了做权限切割。

团队空间

第一期做的每个用户只能看到自己的问卷:


在此基础上拓展空间功能:

  • 个人创建的问卷归属到个人空间

  • 在空间下创建的问卷,归属于空间,可以添加空间用户

表结构调整:

1、新增两张表:

空间表、空间成员表,用于记录空间相关的信息

2、meta 表

新增workspaceId字段用于记录问卷所属空间,为空则不属于任何空间


其中,空间的权限设计,采用了 RBAC(基于角色的访问控制)模型,添加空间成员时给用户分配角色即可。权限点比较多,且便于继续拓展,采用此模型能够降低成员的管理成本,优化用户体验。

后续更多的业务场景可以自行拓展。

问卷协作功能的设计与实现

协作功能允许用户将个人空间下的问卷共享给其他用户,并为协作者分配不同的权限。


当权限系统体量小,用户直接对应具体功能点即可满足系统诉求时,可以考虑使用 ACL 模型作为参考。

协作权限的设计

我们设计了三种协作权限,以满足不同的协作需求:

  • 问卷管理:配置问卷的内容和设置。

  • 问卷数据管理:分析和查看问卷数据。

  • 问卷协作人管理:管理问卷的协作者。


协作的权限设计,我们采用了 ACL 模型,这种权限设计使得权限配置更加灵活,如果引入角色的概念,会产生 7 种角色,反而更加难理解和使用。

协作功能的技术实现

1、新增一个 model

import { Entity, Column } from 'typeorm';import { BaseEntity } from './base.entity';

@Entity({ name: 'collaborator' })export class Collaborator extends BaseEntity { @Column() surveyId: string;

@Column() userId: string;

@Column('jsonb') permissions: Array<string>;}
复制代码

2、新增对 collaborator 操作的 service,因为交互上我们是批量管理协作者,所以核心的功能是批量添加协作者、批量修改权限和批量删除这几个功能

import { Injectable } from '@nestjs/common';import { Collaborator } from 'src/models/collaborator.entity';import { InjectRepository } from '@nestjs/typeorm';import { MongoRepository } from 'typeorm';import { ObjectId } from 'mongodb';import { Logger } from 'src/logger';

@Injectable()export class CollaboratorService { constructor( @InjectRepository(Collaborator) private readonly collaboratorRepository: MongoRepository<Collaborator>, private readonly logger: Logger, ) {} ... async batchCreate({ surveyId, collaboratorList }) { const res = await this.collaboratorRepository.insertMany( collaboratorList.map((item) => { return { ...item, surveyId, }; }), ); return res; }

async changeUserPermission({ userId, surveyId, permission }) { const updateRes = await this.collaboratorRepository.updateOne( { surveyId, userId, }, { $set: { permission, }, }, ); return updateRes; } async batchDelete({ idList, neIdList, userIdList, surveyId, }: { idList?: Array<string>; neIdList?: Array<string>; userIdList?: Array<string>; surveyId: string; }) { const query: Record<string, any> = { surveyId, $or: [], };

if (Array.isArray(userIdList) && userIdList.length > 0) { query.$or.push({ userId: { $in: userIdList, }, }); }

if ( (Array.isArray(idList) && idList.length > 0) || (Array.isArray(neIdList) && neIdList.length > 0) ) { const idQuery: Record<string, any> = { _id: {}, }; if (idList && idList.length > 0) { idQuery._id.$in = idList.map((item) => new ObjectId(item)); } if (neIdList && neIdList.length > 0) { idQuery._id.$nin = neIdList.map((item) => new ObjectId(item)); } query.$or.push(idQuery); } this.logger.info(JSON.stringify(query)); const delRes = await this.collaboratorRepository.deleteMany(query); return delRes; }



updateById({ collaboratorId, permissions }) { return this.collaboratorRepository.updateOne( { _id: new ObjectId(collaboratorId), }, { $set: { permissions, }, }, ); } ...}
复制代码

权限控制的设计与实现

通过权限守卫(Guard)来校验用户权限,确保用户只能进行授权范围内的操作。


我们设计了两个守卫:空间守卫(WorkspaceGuard)和问卷守卫(SurveyGuard),并借助 nestjs 提供的装饰器 @SetMetadata 来给接口配置权限。

空间守卫

具体实现如下:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';import { Reflector } from '@nestjs/core';import { get } from 'lodash';

import { NoPermissionException } from '../exceptions/noPermissionException';

import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';import { ROLE_PERMISSION as WORKSPACE_ROLE_PERMISSION } from 'src/enums/workspace';

@Injectable()export class WorkspaceGuard implements CanActivate { constructor( private reflector: Reflector, private readonly workspaceMemberService: WorkspaceMemberService, ) {}

async canActivate(context: ExecutionContext): Promise<boolean> { const allowPermissions = this.reflector.get<string[]>( 'workspacePermissions', context.getHandler(), );

if (!allowPermissions) { return true; }

const request = context.switchToHttp().getRequest(); const user = request.user; const workspaceIdInfo = this.reflector.get( 'workspaceId', context.getHandler(), );

let workspaceIdKey, optional; if (typeof workspaceIdInfo === 'string') { workspaceIdKey = workspaceIdInfo; optional = false; } else { workspaceIdKey = workspaceIdInfo?.key; optional = workspaceIdInfo?.optional || false; }

const workspaceId = get(request, workspaceIdKey);

if (!workspaceId && optional === false) { throw new NoPermissionException('没有空间权限'); }

if (workspaceId) { const membersInfo = await this.workspaceMemberService.findOne({ workspaceId, userId: user._id.toString(), });

if (!membersInfo) { throw new NoPermissionException('没有空间权限'); }

const userPermissions = WORKSPACE_ROLE_PERMISSION[membersInfo.role] || []; if ( allowPermissions.some((permission) => userPermissions.includes(permission), ) ) { return true; } throw new NoPermissionException('没有权限'); }

return true; }}
复制代码

在接口配置守卫:

@Post(':id')@HttpCode(200)@UseGuards(WorkspaceGuard)@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE])@SetMetadata('workspaceId', 'params.id')async update(@Param('id') id: string, @Body() workspace: CreateWorkspaceDto) {  ....}
复制代码

问卷守卫

问卷守卫的代码相对比较多,大家有兴趣可以查看工程:https://github.com/didi/xiaoju-survey

数据隔离方案

我们上线空间和协作功能后,更多的是对问卷的配置管理做了权限控制,但是我们的回收数据实际上更重要,我们可以把回收数据理解成资产,对于一个 SaaS 化的产品来说,资产是需要进行隔离的,以保障安全性,不同空间下的问卷,回收数据需要进行隔离。

数据库设计方案

数据隔离有几个方案:


  • 数据库表隔离:每个租户使用独立的数据库表,简化权限管理。此方案实现简单,对当前代码的改动也小,我们后续开源计划也是使用此方案。

  • 数据库的隔离:每个租户使用独立的数据库,确保数据隔离性和安全性。此方案稍微复杂,每创建一个空间,需要手动或者自动给改空间分配一个数据库,如果没有现成的数据库,还需要申请或创建数据库,并和空间进行关联,改动相对较大。

  • 数据库集群与分区策略:通过数据库集群和分区提高系统性能和扩展性。此方案更加复杂,如果是有比较成熟的商业化方案,可以考虑此方案,本文暂不考虑此方案。

实现

数据隔离作为迭代的 Feature 进行建设,也欢迎大家认领:server侧系统优化 — 空间数据隔离优化:不同空间进行数据表隔离

结尾

本文介绍了问卷系统的协作和空间功能设计与实现,希望能够给大家带来一些有价值的参考和启发,欢迎大家一起讨论反馈。


关于我们

感谢看到最后,我们是一个多元、包容的社区,我们已有非常多的小伙伴在共建,欢迎你的加入。

Github:XIAOJUSURVEY


社区交流群

Star

开源不易,请star一下 ❤️❤️❤️,你的支持是我们最大的动力。


发布于: 刚刚阅读数: 7
用户头像

XIAOJUSURVEY

关注

@滴滴 github.com/didi/xiaoju-survey 2024-05-20 加入

「快速」打造「专属」问卷系统, 让调研「更轻松」

评论

发布
暂无评论
权限管理:RBAC和ACL在XIAOJUSURVEY的应用_服务端_XIAOJUSURVEY_InfoQ写作社区