# VBase 权限模型设计文档 ## 核心原则 1. **Policy(策略)**:定义"什么权限可以访问哪些接口" 2. **Role(角色)**:Policy 的集合,简化授权 3. **授权关系**:用户通过 Role 或直接与 Policy 关联,并携带数据范围 --- ## 表结构关系 ``` ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Policy │◄────┤ PolicyRoute │ │ Role │ │ (策略定义) │ │ (策略路由绑定) │ │ (角色定义) │ └────────┬────────┘ └──────────────────┘ └────────┬────────┘ │ │ │ Many-to-Many │ Many-to-Many │ │ ▼ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ UserPermission (用户授权) │ │ ├─ user_id: 用户ID │ │ ├─ policy_code: 策略代码 (或直接关联 Policy) │ │ ├─ role_id: 角色ID (可选,与 policy_code 二选一) │ │ ├─ resource: 资源类型 (project/org/...) │ │ ├─ resource_id: 具体资源ID (proj-123, * 表示所有) │ │ ├─ scope: 权限范围 (owner/admin/member) │ │ └─ expire_at: 过期时间 (可选) │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 详细表设计 ### 1. Policy(策略表) 定义权限的本质:对某资源可以执行什么操作 | 字段 | 类型 | 说明 | |------|------|------| | id | string | UUID | | code | string | **唯一标识**,如 `project:read`, `project:write`, `project:admin` | | resource | string | 资源类型:`user`, `org`, `project`, `*`(所有) | | action | string | 操作类型:`create`, `read`, `update`, `delete`, `list`, `*`, `admin` | | effect | string | `allow` / `deny` | | scope | string | 作用域:`platform`(平台级) / `org`(组织级) | | description | string | 描述 | **示例数据:** ```sql -- 项目相关权限 ('p1', 'project:create', 'project', 'create', 'allow', 'org', '创建项目') ('p2', 'project:read', 'project', 'read', 'allow', 'org', '查看项目') ('p3', 'project:write', 'project', 'write', 'allow', 'org', '编辑项目(包含read)') ('p4', 'project:delete', 'project', 'delete', 'allow', 'org', '删除项目') ('p5', 'project:admin', 'project', 'admin', 'allow', 'org', '项目管理员(包含所有)') -- 通配权限 ('p9', 'project:*', 'project', '*', 'allow', 'org', '所有项目权限') ('p0', '*:*', '*', '*', 'allow', 'platform', '超级管理员') ``` **层级关系:** ``` project:admin > project:write > project:read │ │ └─ project:create, project:delete └──────────────┴────────────────────────────────────────────── ``` --- ### 2. PolicyRoute(策略路由绑定表) Policy 与具体接口的绑定关系。一个 Policy 可以绑定多个路由。 | 字段 | 类型 | 说明 | |------|------|------| | id | string | UUID | | policy_id | string | 关联 Policy.id | | domain | string | 应用域,如 `vbase`, `myapp` | | prefix | string | 路由前缀,如 `/api/v1` | | pattern | string | 路由模式,如 `/projects`, `/projects/{id}` | | method | string | HTTP 方法:`GET`, `POST`, `*`, ... | | resource_id_param | string | 从 URL 提取资源ID的参数名,如 `id` | | description | string | 接口描述 | **示例数据:** ```sql -- project:read 权限可以访问以下接口 ('r1', 'p2', 'myapp', '/api/v1', '/projects', 'GET', '', '项目列表') ('r2', 'p2', 'myapp', '/api/v1', '/projects/{id}', 'GET', 'id', '项目详情') ('r3', 'p2', 'myapp', '/api/v1', '/projects/{id}/logs', 'GET', 'id', '项目日志') -- project:write 权限 ('r4', 'p3', 'myapp', '/api/v1', '/projects/{id}', 'PATCH', 'id', '更新项目') ('r5', 'p3', 'myapp', '/api/v1', '/projects/{id}/env', 'PUT', 'id', '更新环境变量') -- project:create 权限 ('r6', 'p1', 'myapp', '/api/v1', '/projects', 'POST', '', '创建项目') -- project:admin 权限(包含所有) ('r7', 'p5', 'myapp', '/api/v1', '/projects/{id}/members', 'GET', 'id', '成员管理') ('r8', 'p5', 'myapp', '/api/v1', '/projects/{id}/settings', '*', 'id', '设置管理') ``` --- ### 3. Role(角色表) Policy 的集合,用于批量授权。 | 字段 | 类型 | 说明 | |------|------|------| | id | string | UUID | | org_id | string | 组织ID(系统角色为空) | | code | string | 角色代码:`admin`, `developer`, `viewer`, `owner` | | name | string | 角色名称 | | policy_codes | string | 关联的 Policy code 列表,逗号分隔 | | is_system | bool | 是否系统预设角色 | **示例数据:** ```sql -- 系统预设角色 ('r1', '', 'owner', '所有者', 'project:admin,org:admin,*:*', true) ('r2', '', 'admin', '管理员', 'project:write,org:read,user:read', true) ('r3', '', 'developer', '开发者', 'project:write,resource:read', true) ('r4', '', 'viewer', '只读用户', 'project:read', true) -- 组织自定义角色 ('r5', 'org-123', 'pm', '项目经理', 'project:admin', false) ``` --- ### 4. UserPermission(用户授权表) 核心授权表,记录"谁对什么资源有什么权限"。 | 字段 | 类型 | 说明 | |------|------|------| | id | string | UUID | | user_id | string | 用户ID | | org_id | string | 组织ID(可选) | | **policy_code** | string | **策略代码**(如 `project:read`) | | **resource** | string | **资源类型**(如 `project`) | | **resource_id** | string | **具体资源ID**(如 `proj-123` 或 `*`) | | granted_by | string | 授权来源:`role`(通过角色) / `direct`(直接授权) | | source_id | string | 来源ID(Role ID 或留空) | | expire_at | timestamp | 过期时间(可选,永久有效为空) | | created_at | timestamp | 创建时间 | **关键设计:** - `resource_id = *` 表示对该类型所有资源的权限 - 数据级权限通过具体的 `resource_id` 实现 - 用户最终权限 = 直接授权 + 角色授权 的并集 **示例数据:** ```sql -- 场景:用户 A -- 1. 是项目 1 的 admin(直接授权) ('up1', 'user-a', 'org-1', 'project:admin', 'project', 'proj-1', 'direct', '', null) -- 2. 是项目 2 的只读成员(直接授权) ('up2', 'user-a', 'org-1', 'project:read', 'project', 'proj-2', 'direct', '', null) -- 3. 通过 developer 角色获得对所有项目的 write 权限 ('up3', 'user-a', 'org-1', 'project:write', 'project', '*', 'role', 'role-dev-id', null) -- 4. 临时授权,3天后过期 ('up4', 'user-a', 'org-1', 'project:admin', 'project', 'proj-3', 'direct', '', '2025-02-20 00:00:00') ``` --- ## 权限检查流程 ``` 请求: GET /api/v1/projects/proj-123/settings Header: X-Org-ID: org-1 │ ▼ ┌─────────────────────────────────────┐ │ 1. 路由匹配 │ │ domain=myapp + method=GET + │ │ pattern=/projects/{id}/settings │ │ │ │ 找到: PolicyRoute.r8 │ │ 对应: Policy.p5 (project:admin) │ │ 提取: resource_id = "proj-123" │ └─────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ 2. 获取用户权限列表 │ │ SELECT * FROM user_permissions │ │ WHERE user_id = 'user-a' │ │ AND resource = 'project' │ │ AND (resource_id = 'proj-123' │ │ OR resource_id = '*') │ └─────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ 3. 权限层级匹配 │ │ 需要的权限: project:admin │ │ │ │ 用户有的权限: │ │ - project:admin (proj-1) ❌ │ │ - project:read (proj-2) ❌ │ │ - project:write (*) ❌ │ │ (write < admin) │ │ │ │ 结果: 拒绝 ❌ │ └─────────────────────────────────────┘ │ ▼ 拒绝,返回 X-Required-URL: /apply-perm?resource=project&action=admin&id=proj-123 ``` --- ## 场景覆盖 ### 场景1:用户是项目 owner,拥有所有权限 ```sql INSERT INTO user_permissions (user_id, policy_code, resource, resource_id) VALUES ('user-a', 'project:admin', 'project', 'proj-1') -- 自动包含 project:read/write/delete/create ``` ### 场景2:平台管理员,拥有所有资源的所有权限 ```sql -- 方案一:通配符 INSERT INTO user_permissions (user_id, policy_code, resource, resource_id) VALUES ('admin', '*:*', '*', '*') -- 方案二:绑定 owner 角色 -- Role.owner 包含 policy_codes: '*:*' ``` ### 场景3:组织管理员,拥有组织内所有项目权限 ```sql -- 组织 admin 角色包含 project:* -- 在组织上下文内生效 INSERT INTO user_permissions (user_id, org_id, policy_code, resource, resource_id, granted_by) VALUES ('user-a', 'org-1', 'project:*', 'project', '*', 'role') ``` ### 场景4:用户加入多个项目,权限不同 ```sql -- 项目1: admin INSERT INTO user_permissions VALUES ('u1', 'project:admin', 'project', 'proj-1', ...) -- 项目2: read INSERT INTO user_permissions VALUES ('u1', 'project:read', 'project', 'proj-2', ...) -- 项目3: write INSERT INTO user_permissions VALUES ('u1', 'project:write', 'project', 'proj-3', ...) ``` --- ## 与旧方案对比 | 维度 | 旧方案 (RoutePolicy) | 新方案 (Policy + UserPermission) | |------|---------------------|--------------------------------| | 核心思想 | 接口白名单 | 资源操作权限 | | 配置粒度 | 每个接口单独配置 | 按 Resource + Action 配置 | | 数据级权限 | 难实现 | 通过 resource_id 字段 | | 通配授权 | 不支持 | 支持 `*` | | 层级权限 | 无 | admin > write > read | | 新增接口成本 | 高(需给所有角色加策略) | 低(绑定到 Policy 即可)| | 角色数量 | 多(每项目配角色) | 少(复用系统角色 + 数据范围)| --- 请确认此设计后,我开始修改代码。