4. ACL核心算法:权限检查、合并与继承
ACL 这东西,说白了就是一套「谁能不能干什么」的规则。但规则多了,怎么高效判断?怎么处理冲突?怎么让权限自动往下传?这就是今天要聊的三个核心算法。
我刚开始接触 ACL 时,觉得不就是查个表嘛。后来在项目中遇到一个用户既有部门角色又有项目角色,权限叠在一起,结果该有的功能打不开,不该有的反而能访问……嗯,从那以后我才认真研究起这些算法来。
4.1 权限检查算法
权限检查是 ACL 最基础的操作。用户发起一个请求,系统要回答:「这人能访问这个资源吗?」
算法其实不复杂,核心就三步:
- 定位:找到用户对应的所有 ACL 条目(直接授予的、通过角色获得的)
- 匹配:检查这些条目中是否包含目标资源
- 裁决:根据匹配结果返回 允许/拒绝
关键原则:大多数 ACL 实现采用「显式拒绝优先」策略。也就是说,只要有一条 ACL 条目明确拒绝了该操作,不管有多少条允许,最终结果都是拒绝。
我习惯把检查算法写成这样:
function checkAccess(user, resource, operation):
// 1. 收集用户所有 ACL 条目
entries = getACLEntries(user)
// 2. 先看有没有显式拒绝
for entry in entries:
if entry.resource == resource
and entry.operation == operation
and entry.effect == DENY:
return DENY // 一票否决
// 3. 再看有没有显式允许
for entry in entries:
if entry.resource == resource
and entry.operation == operation
and entry.effect == ALLOW:
return ALLOW
// 4. 默认拒绝
return DENY
避坑指南:我曾经在项目中忘了处理「通配符资源」的情况。比如用户对 /project/* 有读权限,但代码只精确匹配 /project/123。结果用户死活打不开项目详情页。记得加上前缀匹配或正则匹配的逻辑。
4.2 ACL 合并算法
一个用户往往有多个身份。比如在公司里,他既是「研发部成员」,又是「项目A管理员」。这两个身份可能对同一个资源有不同的权限设置。怎么合并?
合并算法要解决的就是「多源权限冲突」的问题。常见的策略有三种:
| 策略 | 规则 | 适用场景 |
|---|---|---|
| 拒绝优先 | 任何来源的 DENY 都覆盖 ALLOW | 安全敏感系统 |
| 允许优先 | 任何来源的 ALLOW 都覆盖 DENY | 协作开放系统 |
| 最具体优先 | 粒度更细的条目优先级更高 | 复杂层级组织 |
我个人推荐「拒绝优先 + 最具体优先」的组合策略。说白了就是:
- 先按资源匹配的精确度排序
- 同精度下,拒绝优先
举个例子:
用户A的权限来源:
- 角色「研发部」:允许读取 /project/*
- 角色「项目B成员」:拒绝读取 /project/B
合并结果:
- 访问 /project/A → 允许(只有研发部规则匹配)
- 访问 /project/B → 拒绝(项目B的规则更具体,且拒绝优先)
注意:合并算法一定要考虑性能。如果每次请求都把所有 ACL 条目拉出来排序,系统扛不住的。我建议在用户登录或角色变更时,提前计算好合并后的权限快照,存到缓存里。
4.3 ACL 继承算法
继承解决的是「权限传递」问题。比如你给一个文件夹设置了权限,里面的文件应该自动继承。不然每个文件都要单独配,那不得累死?
继承算法的核心逻辑是:
- 向上查找:如果当前资源没有明确的 ACL 条目,就往父节点找
- 逐层递归:一直找到根节点为止
- 就近覆盖:子节点如果有自己的设置,就覆盖继承来的
我画了一张图,帮你理解这个流程:
代码实现也不复杂:
function getEffectiveACL(resource, user):
// 先看当前资源有没有直接 ACL
acl = getDirectACL(resource, user)
if acl != null:
return acl
// 没有就找父节点
parent = getParent(resource)
if parent == null:
return DEFAULT_DENY // 到根了还没找到
// 递归向上
return getEffectiveACL(parent, user)
避坑指南:我曾经在项目中遇到一个性能问题——文件层级很深,每次访问都要递归几十层。后来加了「路径缓存」,把每个节点的继承结果缓存起来,只有 ACL 变更时才清除缓存。性能提升了 10 倍不止。
4.4 三种算法的配合使用
在实际系统中,这三个算法不是孤立的。它们通常这样配合:
- 继承算法先算出用户对某个资源的「有效 ACL 列表」
- 合并算法把多个来源的 ACL 合并成一份
- 检查算法用合并后的结果做最终裁决
你想想看,如果没有继承,每个文件都要配权限,维护成本太高。如果没有合并,多角色用户就乱套了。如果没有检查算法,那 ACL 就是个摆设。
我的建议:设计 ACL 系统时,先把这三个算法的接口定义清楚。接口稳定了,具体实现可以慢慢优化。我在项目中吃过亏——一上来就写具体逻辑,结果后面要加缓存、加审计日志,改得想哭。