SQL 模型已定义完整的权限体系(sys_menu, sys_role, sys_role_menu, sys_role_data_scope_detail, sys_user_role),但后端和前端只实现了 CRUD 和 UI 配置,权限执行层几乎为空。本次修复补全所有缺失的权限执行逻辑。
| # | 模块 | 优先级 | 描述 |
|---|---|---|---|
| 1 | 后端菜单权限守卫 | P0 | 将已有的 require_menu_code() 应用到所有业务路由 |
| 2 | 后端数据权限过滤 | P0 | 创建 data_scope 服务,在业务查询中按部门/用户/自定义范围过滤 |
| 3 | 前端用户角色选择器 | P1 | 用户管理弹窗中添加角色多选下拉框 |
| 4 | 前端按钮级权限 | P1 | 业务页面按钮添加 v-auth / hasAuth 权限控制 |
| 5 | admin 无权限码安全漏洞 | P2 | 修复空权限码返回 {all: true} 的问题 |
| 6 | 多角色权限合并 | P2 | 后端菜单码已天然合并;数据权限需实现并集逻辑 |
app/core/menu_guard.py 已定义 require_menu_code(menu_code) 函数在业务路由的每个端点上叠加 Depends(require_menu_code("xxx")) 作为第二层守卫。
原则:
require_admin_or_super_admin,仅 super_admin 可操作require_menu_codexxx:list,创建用 xxx:add,更新用 xxx:edit,删除用 xxx:deleterequire_menu_code 内部已先调用 require_admin_or_super_admin,所以路由只需一个 Depends影响文件:
backend/app/api/basic_info.py — 客户、细胞样本类型、采集地区、采集协议号、样本编码等backend/app/api/operations.py — 渠道、订单、质检等运营接口backend/app/api/collection_protocol.py — 采集协议backend/app/api/sample_reception.py — 样本接收backend/app/api/admin.py — 仅业务部分(用户管理加 system:user:list 等)perms 值来源:直接使用 sys_menu 表中已定义的 perms 字段值。
当前 SQL 中 sys_menu 大多只有 M(目录) 和 C(页面) 类型。为了让 require_menu_code 能精确控制到增删改操作,需要在 SQL 中为每个 C 类型菜单补充 F 类型按钮权限:
xxx:list — 查看列表xxx:add — 新增xxx:edit — 编辑xxx:delete — 删除这些 F 类型记录的 parent_id 指向对应的 C 类型菜单。此步骤为 数据初始化,需生成 SQL 脚本。
sys_role.data_scope 字段存在(1-5 级别)sys_role_data_scope_detail 表和 CRUD 已实现backend/app/core/data_scope.pydef apply_data_scope(query, model, user_info: dict, db: Session) -> query:
"""在 SQLAlchemy query 上叠加数据权限过滤条件。
参数:
query: 原始 SQLAlchemy query 对象
model: ORM 模型类(需要有 create_dept_id, create_by 字段)
user_info: get_current_user 返回的 dict(含 user_id, dept_id)
db: 数据库 session
返回:
添加了数据权限过滤条件的 query
"""
| data_scope | 条件 |
|---|---|
1 全部数据 |
无附加过滤 |
2 本部门 |
model.create_dept_id == user.dept_id |
3 本部门及以下 |
model.create_dept_id IN (user.dept_id + 子部门ID列表) |
4 仅本人 |
model.create_by == user.user_id |
5 自定义 |
并集:(create_dept_id IN 自定义部门) OR (create_by IN 自定义用户) |
用户可能有多个角色,每个角色有自己的 data_scope。合并策略:
OR 条件在 admin.py 等路由的 list 接口中:
@router.get("/customers")
async def list_customers(
...,
user: dict = Depends(require_menu_code("biz:customer:list")),
db: Session = Depends(get_db),
):
query = db.query(BizCustomer).filter(BizCustomer.del_flag == 0, ...)
query = apply_data_scope(query, BizCustomer, user, db)
...
需要 user_info dict 中包含 dept_id。当前 JWT payload 中 region_id 即为 dept_id(在 public.py 登录时已设置)。
UserCreateBody / UserUpdateBody 已支持 roleIds 字段GET /web/admin/roles/options 已返回角色下拉列表web-admin/src/views/system/user/index.vue 表单中没有角色选择器在 user/index.vue 的创建/编辑弹窗中:
el-select multiple)GET /web/admin/roles/options 获取角色列表roleIds 字段参考:modules/user-dialog.vue 中已有类似实现,可参考其角色选择逻辑。
v-auth 指令已实现(读取 route.meta.authList)hasAuth() composable 已实现在每个业务页面的操作按钮上添加权限控制:
<!-- 新增按钮 -->
<el-button v-if="hasAuth('add')" type="primary" @click="handleAdd">新增</el-button>
<!-- 编辑按钮 -->
<el-button v-if="hasAuth('edit')" link type="primary" @click="handleEdit(row)">编辑</el-button>
<!-- 删除按钮 -->
<el-button v-if="hasAuth('delete')" link type="danger" @click="handleDelete(row)">删除</el-button>
权限标识来源:sys_menu 中 menu_type='F' 的按钮权限记录的 perms 字段。
注意:当前 v-auth 指令从 route.meta.authList 读取,而 hasAuth() 从 userStore.getUserInfo.buttons 读取。需确保按钮权限列表在登录时正确加载到这两个位置。
src/views/system/user/index.vuesrc/views/system/role/index.vue(角色管理本身也需按钮权限)src/views/basic-info/customer/index.vuesrc/views/basic-info/cell-sample-type/index.vuesrc/views/basic-info/collection-region/index.vuesrc/views/basic-info/collection-protocol-no/index.vuesrc/views/basic-info/sample-code/index.vuebackend/app/api/public.py 中 /web/auth/menu-codes 端点:
if not codes:
return {"isSuperAdmin": False, "all": True, "menuCodes": []} # ← 安全漏洞
当 admin 用户没有任何菜单权限时,返回 all: true,前端将此视为拥有所有权限。
后端:当 codes 为空时返回 all: false,不再赋予全部权限。
if not codes:
return {"isSuperAdmin": False, "all": False, "menuCodes": []}
前端:确保 MenuProcessor.ts 正确处理 all: false + 空 menuCodes 的情况——显示无菜单/无权限页面。
iter_user_menu_perm_codes() 已通过 sys_user_role → sys_role_menu → sys_menu 链路查询,天然支持多角色菜单并集(DISTINCT 去重)。无需修改。
新增函数 resolve_user_data_scope_union(db, user_id) -> DataScopeFilter:
data_scope 和 scope_detailscreate_dept_id IN 部门集合 OR create_by IN 用户集合