02-ACL模型详细设计-资源授权权限继承与共享控制
ACL 模型详细设计:资源授权、权限继承与共享控制
RBAC 可以表达“项目管理员拥有文档分享权限”,却不擅长回答“Alice 能否下载 D1001”。当权限需要精确到某个文件、项目、看板或报表时,ACL 更自然。
ACL 的基本思路是:在资源上保存一组访问控制条目 ACE,每条条目说明哪个主体对该资源执行什么操作时允许或拒绝。
1. ACL 与 ACE
一条 ACE 至少包含:
subject_type + subject_id
resource_id
action
effect
valid_from / valid_until
source_type / source_id
granted_by
示例:
Subject: group:PROJECT_A_MEMBERS
Resource: document:D1001
Action: read
Effect: ALLOW
Source: PROJECT_MEMBERSHIP
ACL 是资源上的 ACE 集合。Owner 表示资源所有者,但所有者拥有哪些能力仍由资源类型策略定义,不能把 Owner 当作无限管理员。
2. 主体和操作粒度
ACL 主体可以是用户、用户组、组织、角色、外部用户或服务账号。项目文档通常以用户组作为主要授权对象,减少逐人配置;对少数例外再使用用户直接 ACE。
操作应按风险拆分:
read、update、delete、download、share、manage-permission
“能读”不等于“能下载”。在线阅读可以执行动态脱敏和水印,下载后的文件脱离在线控制,因此应独立授权和审计。
3. Allow、Deny 和冲突
专题默认在同一 ACL 继承边界内采用 Deny 优先:
Project-A 项目组 ALLOW read
Alice DENY read
→ Alice 最终 DENY
Deny 适合明确例外,例如外部成员禁止下载机密文件。它不应被滥用为另一套复杂角色系统,否则管理员很难解释多个组、多个父目录和多个直接条目的冲突。
系统级强制 Deny 不属于普通 ACL:租户不匹配、主体已禁用、合规规则禁止外部访问时,即使资源 ACE 为 Allow 也不能放行。
4. 权限继承
资源通常形成树:项目目录、子目录、文档。默认继承方向是父资源到子资源,不能由子文档反推对父目录的全部权限。
INHERIT
子资源持续继承父级 ACL。父目录新增或撤销成员后,子资源自动变化,适合绝大多数项目文档。
BREAK_COPY
停止继承时,把当前有效继承条目复制为本地条目。以后父级变化不再影响子资源,适合从某一时刻开始独立管理的文档。
BREAK_EMPTY
停止继承并清空继承条目,只保留重新配置的本地授权。适合高度保密的子目录,但风险很高,应要求单独权限、二次确认和审计。
停止继承只影响普通 ACL,不能移除租户隔离、资源分类、外部用户限制和强制 MFA 等系统策略。
5. 恢复继承和资源移动
恢复继承时需要明确如何处理本地 ACE:
- 保留本地 ACE,再叠加父级;
- 清空本地 ACE,完全跟随父级;
- 先显示影响预览,由管理员选择。
资源移动到新目录时,默认保留本地 ACE,并把继承来源切换到新父目录。移动前必须计算权限差异,尤其检查外部成员是否因为新目录的宽松 ACL 获得访问能力。跨租户移动不能被普通资源移动实现,应走数据迁移流程。
6. 资源所有权
文档所有者通常可以编辑、分享和管理协作者,但不一定可以:
- 修改机密级别;
- 删除审计记录;
- 绕过保留策略;
- 把资源分享给任意外部租户;
- 跳过管理员设置的下载禁令。
资源类型应该定义所有权默认能力。例如付款单创建人可以修改草稿、提交和撤回,却不能审批本人创建的付款单。
7. 分享模型
指定用户或用户组分享通常生成 ACE;匿名或外部链接分享则需要独立 Share Link,至少包含:
resource_id
token_hash
allowed_actions
expires_at
password_hash
max_access_count
watermark_policy
status
created_by
访问链接时仍要校验资源状态、租户规则、有效期、访问次数和动态策略。不能把随机 URL 当作唯一安全机制。撤销分享后,相关缓存和 CDN 私有访问凭证也要同步失效。
8. 数据库设计
CREATE TABLE auth_resource (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
resource_type VARCHAR(64) NOT NULL,
resource_key VARCHAR(128) NOT NULL,
parent_resource_id BIGINT NULL,
owner_principal_id BIGINT NULL,
inheritance_mode VARCHAR(32) NOT NULL DEFAULT 'INHERIT',
status VARCHAR(32) NOT NULL,
version BIGINT NOT NULL DEFAULT 1,
UNIQUE KEY uk_resource (tenant_id, resource_type, resource_key),
KEY idx_parent (tenant_id, parent_resource_id)
);
CREATE TABLE auth_acl_entry (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
resource_id BIGINT NOT NULL,
subject_type VARCHAR(32) NOT NULL,
subject_id BIGINT NOT NULL,
action_code VARCHAR(64) NOT NULL,
effect VARCHAR(16) NOT NULL,
source_type VARCHAR(32) NOT NULL,
source_id BIGINT NULL,
granted_by BIGINT NULL,
valid_from DATETIME NULL,
valid_until DATETIME NULL,
status VARCHAR(32) NOT NULL,
version BIGINT NOT NULL DEFAULT 1,
KEY idx_resource_action (tenant_id, resource_id, action_code, status),
KEY idx_subject (tenant_id, subject_type, subject_id, status)
);
不要把角色分配、组织成员、ACL、资源关系全部塞进一张万能 relation 表。它们生命周期、索引方向和约束规则不同。
9. 权限计算
单资源检查可以按以下步骤:
- 校验租户和资源状态。
- 展开 Subject 的直接身份、用户组、组织和有效角色。
- 从当前资源向父级查找可继承 ACL。
- 合并当前资源本地 ACE。
- 过滤过期、撤销和不适用 Action。
- 应用 Deny/Allow 规则。
- 执行资源分类与 ABAC 强制策略。
- 返回决策和来源链。
伪代码:
AclDecision checkAcl(AclRequest request) {
Resource resource = resourceRepo.requireInTenant(
request.tenantId(), request.resourceId());
Set<SubjectRef> subjects = subjectResolver.expand(request.subjectId());
List<AclEntry> entries = aclRepo.findEffectiveEntries(
resource, subjects, request.action(), request.at());
if (entries.stream().anyMatch(AclEntry::isDeny)) {
return AclDecision.deny("DENY_ACL", entries);
}
if (entries.stream().anyMatch(AclEntry::isAllow)) {
return AclDecision.allow(entries);
}
return AclDecision.deny("DENY_NO_ACL");
}
真实系统还需要把强制策略放在普通 ACL 之前或之后的固定层级中,避免业务 ACE 覆盖合规限制。
10. 两种查询方向
“这个资源授权给了谁”容易:按 resource_id 查询 ACL 即可。
“这个用户能访问哪些资源”困难得多,需要展开用户组、组织和角色,再反查直接 ACE、计算父级继承、应用 Deny 和有效期,并且还要支持分页和排序。常见优化方式包括:
- 对直接 ACL 建主体反向索引;
- 对资源树建立闭包表或物化路径;
- 对高频查询维护可访问资源倒排索引;
- 使用搜索引擎过滤授权后的候选资源;
- 对用户组变更做选择性缓存失效;
- 对 List Objects 使用专门算法,而不是循环调用单资源 Check。
大规模 ReBAC 系统通常把关系元组和权限模型分开管理,提供 Check、ListObjects、ListUsers 等不同接口。
11. 缓存与失效
可缓存:
- 用户有效用户组;
- 资源父链;
- 资源局部 ACL;
- 经版本标记的最终决策。
必须触发失效的事件:ACL 修改、资源移动、停止继承、用户退出组、分享撤销、资源敏感级别变化。最终决策缓存键至少包含 tenant、subject、resource、action、ACL version 和 policy version。
12. 常见错误
- 只保护文档详情,不保护下载、历史版本和附件接口。
- 链接足够随机就不检查权限。
- 用户退出项目组后,预计算索引不更新。
- 子资源的 Allow 静默覆盖父级强制 Deny。
- 移动文档后没有重新计算继承 ACL。
- 删除资源但外链或对象存储签名 URL 仍可访问。
- 把 Owner 当成绕过所有策略的超级管理员。
13. 适用边界
ACL 适合资源实例分享、项目协作和文件树权限,不适合独立承担大规模功能权限,也不擅长复杂金额、设备和风险判断。通常应与 RBAC、ABAC 和 ReBAC 组合使用。
评论