第12章 权限过滤、撤权与缓存失效
权限系统最危险的时刻,常常发生在合法访问之后。Alice 昨天有权读取项目,今天被移出;搜索缓存仍在,浏览器还有一个命中对象。若回答函数把“曾经检索到”理解为“现在仍可展示”,撤权就没有真正生效。
先有可信身份,再有检索
实验要求调用方传入 Context,租户和用户只能包含受限身份字符。没有该对象就拒绝查询,未知租户看不到内容。但这只是接口契约,不是认证方案;任何能任意创建 Python 对象的调用者,都可以构造不同身份。
上线时应由服务端认证层产生上下文,把令牌、组织成员关系和实际租户映射验证完毕,再调用检索。不能把用户提交的两个字符串直接包装成 Context,然后声称系统完成了企业授权。维护方法 status() 也没有终端用户权限检查,必须保持在受信管理边界内。
两个阶段都需要授权
词面查询在 SQL 中先限制租户与 ACL,再排序限额;向量查询先从数据库读取允许的片段,再计算余弦。因此当前实现不会先把不可见原文交给向量评分循环,再事后隐藏结果。空 ACL 拒绝全部用户,星号只在同一租户条件下生效。
回答时再次检查 ACL 与权限版本,弥补搜索和输出组装之间的状态变化。搜索结果携带的用户也要与当前上下文相同,避免把 Bob 的命中对象交给 Alice 复用。测试同时覆盖跨租户同名文档、跨用户命中和空权限列表。
撤权演示先以 Alice 搜索远程办公,再调用 set_acl() 只保留 Bob。旧 Alice 命中不能形成摘录,新搜索不再出现远程办公文档;Bob 的新查询仍能访问。这既证明拒绝发生,也证明没有把撤权错误实现成对所有人删除文档。
权限版本独立推进
set_acl() 要求调用者给出 expected_acl_revision,与数据库当前值一致才替换列表,随后版本加一。两个管理员并发修改时,较旧的写入会得到 stale_acl_revision,必须重新读取策略后决策。
正文更新保留当前 ACL,所以携带旧成员列表的新内容事件不会恢复已撤销权限。相反,显式恢复已删除文档会建立新的 ACL;调用者应先确认恢复依据,而不是自动沿用删除前的缓存策略。
实验没有证明什么
共享 FTS 表的全局词频会影响分数,向量扫描耗时也可能随可见片段数变化。本书没有审计计时、统计或资源竞争侧信道;测试通过不等于严格租户非干扰。高隔离场景应考虑租户独立索引、独立资源和专门的威胁分析。
撤权以数据库当前读快照和回答组装边界为准,不能收回已经发出的原文。外部缓存、追踪日志与用户终端也需要自己的保留和清除协议。这里的复核使旧命中失去继续输出的资格,没有承诺网络传输与权限变更形成一个全局原子事务。
把权限写进每个阶段的契约,比给最终页面加一个“仅内部可见”标签更可靠。接下来运行完整实验,亲自查看这些状态变化与拒绝结果。