Appearance
L02-12 多租户隔离:检索层的硬边界
全局中心内容:租户隔离必须在检索层做过滤下推,靠 Prompt 或事后过滤都是不安全的。 全局讲解主线:一次串数据事故 → 三种隔离方案对比 → 过滤下推 → ACL → 上下文注入 → 验收。
P1 · 产出页
中心内容:tenantId + acl 双重过滤下推到向量库,缺一不可。
- 讲解技巧
- 开场用一个真实感很强的事故:A 公司的客服问「我们的退款政策」,答案里出现了 B 公司的政策原文。
- 强调后果:这不是 bug,是数据泄露事件。
- 时长:20s
P2 · 三种隔离方案
中心内容:物理隔离 / 库内过滤 / 事后过滤。
页面内容:成本、安全性、运维复杂度三维对照
讲解技巧
- 明确推荐:库内过滤(同一张表 + tenantId 过滤) 是绝大多数场景的最优解。
- 物理隔离只在强合规要求(金融/政务)下才值得,因为运维成本是 N 倍。
时长:5min
P3 · 过滤下推
中心内容:过滤条件要在向量检索之前生效,不是检索之后。
页面内容:正确写法(filterExpression)vs 错误写法(先召回再筛)
讲解技巧
- 这是本节的技术核心:先召回再筛会导致「别的租户的内容被塞进 Prompt」,即便最后被过滤掉,也已经产生了泄露风险。
- 另外讲准确率:如果 90% 的数据是别人的,top50 里可能只剩 5 条自己的,召回质量崩塌。
时长:7min
P4 · ACL 细粒度
中心内容:tenantId 管「是不是你的」,acl 管「你有没有权限看」。
页面内容:acl 字段设计与检索时的表达式拼接
讲解技巧
- 用例子区分:同一租户内,薪酬制度只有 HR 能看,员工手册全员可见。
- 提醒:acl 表达式要用参数化构造,别字符串拼接,防止注入。
时长:5min
P5 · 上下文注入
中心内容:tenantId 从认证上下文取,绝不接受前端传参。
页面内容:TenantContext + 拦截器
讲解技巧
- 这条要当成红线讲:任何一处允许
tenantId从请求参数传入,整个隔离就形同虚设。 - 给出工程做法:ThreadLocal + 拦截器,检索层统一从 ThreadLocal 取,业务代码不接触。
- 这条要当成红线讲:任何一处允许
时长:6min
P6 · 编码:过滤器构造
中心内容:统一在一个地方拼装过滤表达式。
页面内容:FilterExpressionBuilder 用法与组合条件
讲解技巧
- 强调拼装逻辑只能有一处,多处拼装必然出现某条链路漏过滤。
- 加一个断言:检索入口处强制校验表达式里包含 tenantId,缺失直接抛异常。
时长:6min
P7 · 验收测试
中心内容:写跨租户的攻击性测试用例。
页面内容:测试用例清单(跨租户、越权、无租户上下文)
讲解技巧
- 这是最该写进 CI 的测试:模拟 A 租户查询,断言结果 100% 属于 A。
- 补一条:无租户上下文时应该拒绝服务,而不是「查全库」。
时长:4min
P8 · 避坑与小结
中心内容:隔离是安全底线,不能为了召回率开后门。
- 讲解技巧
- 收尾强调:「召不到」是体验问题,「串数据」是事故。二者冲突时永远选前者。
- 引出下一节:用户的问题本身不适合检索怎么办——02-13 查询改写。
- 时长:2min
讲师备忘
| 项 | 内容 |
|---|---|
| 课前必做 | 准备跨租户事故描述;准备过滤前后结果对照截图 |
| 最容易超时处 | P3 过滤下推,容易展开到数据库执行计划 |
| 学员最常问 | 「单租户场景也要做吗?」答:做,成本极低且将来扩展零成本 |
| 现场备用 | 无法演示多租户 → 用两条带不同 tenantId 的数据手工验证 |