Skip to content

权限设计 ​

概述 ​

组成 ​

权限体系由三层组成:以 RBAC 为基础的功能权限、基于部门范围的数据权限,以及前端的菜单与按钮权限。三者共用同一套「用户—角色—菜单(权限码)」数据模型,后端在请求链路中统一校验,前端按权限码做菜单渲染与按钮控制。

核心模型 ​

权限的载体是「菜单(system_menu)」。菜单既能驱动前端路由,又承载权限标识:

  • 菜单类型:1 目录、2 菜单、3 按钮、4 外链/内嵌。
  • 权限标识 permission:按钮型菜单必填,格式为冒号分隔的资源码,如 system:user:query、system:user:create。
  • 角色(system_role)与菜单为多对多(system_role_menu)。
  • 用户(system_user)与角色为多对多(system_user_role)。

登录时后端会联表加载用户的部门、启用状态的角色及其菜单,并在整个请求周期内复用。

功能权限 ​

功能权限回答「当前用户能不能调用这个接口」。

使用方式 ​

在路由上通过 AuthPermission 依赖声明所需权限码,鉴权由框架自动完成:

python
from app.framework.security.auth import AuthPermission, AuthSchema

@UserRouter.get("/list")
async def get_user_list_controller(
    auth: Annotated[AuthSchema, Depends(AuthPermission(["system:user:query"]))],
) -> JSONResponse:
    ...
  • 传入权限码列表,用户持有其中任意一个即通过(any 语义)。
  • 传空列表 AuthPermission():仅需登录,不做码校验(如查看个人资料)。
  • check_data_scope=False:关闭本次请求的数据权限过滤(如获取用户精简列表这类公共选择器)。

校验逻辑 ​

AuthPermission 在校验时:

  1. 空权限要求、或包含通配符 * / *:*:* 时直接放行;
  2. 聚合用户所有启用状态角色下、且菜单启用状态的权限码(取并集);
  3. 用户不持有任一所需权限码时抛出 403(无权限操作)。

权限码随用户角色动态变化,角色相关变更会清空 role 命名空间缓存,保证下次请求重新读取最新权限。

数据权限 ​

数据权限回答「当前用户能看哪些数据行」,粒度到部门与归属人,作用于查询阶段。

策略 ​

每个模型可在类上声明数据权限策略(__permission_strategy__),取值来自 PermissionFilterStrategy:

  • NONE:不过滤。用于平台级公共数据(如字典、菜单、角色本身),由业务层自行收窄。
  • DATA_SCOPE(默认):按角色的部门数据范围过滤。
  • OWN:仅本人数据,默认按 creator 列归属当前用户。

此外,模型可声明参与过滤的列(均有默认值,按需覆盖):

  • __dept_column__:所属部门列,默认 dept_id。
  • __user_column__:归属用户列,默认 creator。

数据范围 ​

角色侧通过 data_scope 与 data_scope_dept_ids 描述可见范围,对应 DataScope 常量:

值含义
1 ALL全部数据
2 DEPT_CUSTOM指定部门
3 DEPT_ONLY仅本部门
4 DEPT_AND_CHILD本部门及以下
5 SELF仅本人

多个角色的范围按并集合并:每个角色独立计算可访问部门集合再汇总,避免不同角色的范围互相放大。无角色的用户降级为「仅本人数据」。

自动叠加 ​

数据权限由 CRUDBase.__filter_permissions 在查询构建时自动叠加,业务代码无需手动拼接条件:

python
filter_obj = Permission(model=self.model, auth=self.auth)
return await filter_obj.filter_query(sql)

解析结果会缓存在 AuthSchema.data_permission 上,同一次请求内多次查询复用,避免重复加载部门表。AuthPermission 在切换 check_data_scope 时会清空该缓存,保证一致性。

最小权限原则 ​

当模型声明的部门列/归属列不存在,或解析出的权限为空(有授权但无可用比对列)时,构造「空集条件」(false())拒绝全部数据,防止越权泄露。

权限分配管理 ​

权限的维护通过 module_system/permission 模块提供的能力完成,核心由 PermissionService 编排:

  • 查询 / 分配「角色—菜单」:维护角色可访问的菜单与权限码。
  • 分配「角色数据权限」:设置角色的 data_scope 及指定部门集合。
  • 查询 / 分配「用户—角色」:维护用户拥有的角色集合。

这些接口本身同样受功能权限保护(如 system:permission:assign-role-menu、system:user:update)。

前后端协同 ​

权限下发 ​

登录成功后,前端调用 getAuthPermissionInfoApi,后端返回:

  • user:用户信息(含 dept_id 等)。
  • roles:角色标识列表。
  • menus:可访问菜单(驱动路由与侧边栏)。
  • permissions:权限码列表。

前端将其写入状态:

ts
accessStore.setAccessMenus(authPermissionInfo.menus);
accessStore.setAccessCodes(authPermissionInfo.permissions);
userStore.setUserRoles(authPermissionInfo.roles);

前端鉴权 ​

packages/effects/access 提供两套能力:

  • useAccess():
    • hasAccessByCodes(codes):基于权限码判断,匹配任一即通过。
    • hasAccessByRoles(roles):基于角色标识判断(前端模式下使用)。
  • v-access 指令:按 accessMode 选择按码或按角色校验,无权限时直接移除元素。
vue
<!-- 按权限码控制按钮显隐 -->
<Button v-access:code="'system:user:create'">新增用户</Button>
<!-- 按角色控制(前端模式) -->
<Button v-access:role="['admin']">管理员操作</Button>
ts
import { useAccess } from '@vben/access';

const { hasAccessByCodes } = useAccess();
const canEdit = hasAccessByCodes(['system:user:update']);

数据权限的前端配置 ​

角色「数据权限」弹窗(assign-data-permission-form.vue)对应后端的 DataScope:选择范围后,若为「指定部门」则额外展示部门树供勾选,提交时调用 assignRoleDataScope 写入 dataScope 与 dataScopeDeptIds。前端范围枚举 SystemDataScopeEnum 与后端 DataScope 常量保持一致。

开发实践 ​

新增一个受保护的接口,建议遵循以下步骤:

  1. 声明权限码:在菜单管理中为对应按钮登记权限码(如 system:order:export)。
  2. 后端加依赖:路由参数上挂 AuthPermission(["system:order:export"]);若接口涉及列表查询且需要行级隔离,保持 check_data_scope 默认开启。
  3. 模型声明策略:业务模型按需设置 __permission_strategy__、__dept_column__、__user_column__,默认即按 dept_id/creator 过滤。
  4. 前端控制按钮:在页面使用 v-access:code 或 hasAccessByCodes 控制操作入口,避免无权限用户看到不可用按钮。

提示:平台级公共资源(字典、菜单、角色等)的模型策略应设为 NONE,否则会被数据权限误伤;但若资源本身需要按用户隔离,请在业务层显式收窄查询范围。