外观
权限设计
概述
组成
权限体系由三层组成:以 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 在校验时:
- 空权限要求、或包含通配符
*/*:*:*时直接放行; - 聚合用户所有启用状态角色下、且菜单启用状态的权限码(取并集);
- 用户不持有任一所需权限码时抛出
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 常量保持一致。
开发实践
新增一个受保护的接口,建议遵循以下步骤:
- 声明权限码:在菜单管理中为对应按钮登记权限码(如
system:order:export)。 - 后端加依赖:路由参数上挂
AuthPermission(["system:order:export"]);若接口涉及列表查询且需要行级隔离,保持check_data_scope默认开启。 - 模型声明策略:业务模型按需设置
__permission_strategy__、__dept_column__、__user_column__,默认即按dept_id/creator过滤。 - 前端控制按钮:在页面使用
v-access:code或hasAccessByCodes控制操作入口,避免无权限用户看到不可用按钮。
提示:平台级公共资源(字典、菜单、角色等)的模型策略应设为
NONE,否则会被数据权限误伤;但若资源本身需要按用户隔离,请在业务层显式收窄查询范围。