avatar

一条在知识海洋的咸鱼

这个家伙很懒,啥也没有留下😋

  • Linux
  • OCJP
  • Java核心技术卷
  • J2EE相关标准
  • 深入理解Java虚拟机
  • NIO与SOcket编程技术指南
  • Java多线程编程核心技术
  • Redis开发与运维
  • Spring Cloud Alibaba 微服务原理与实践
  • DevOps
  • Docker
  • MySQL必知必会
  • AI自学路线
  • Spring Boot 编程思想(核心篇)
  • 首页
主页 26 权限隔离:让无权内容无法进入 RAG 上下文
文章

26 权限隔离:让无权内容无法进入 RAG 上下文

发表于 2天前 更新于 2天前
作者 Administrator
47~60 分钟 阅读

权限隔离:让无权内容无法进入 RAG 上下文

RAG 权限隔离的底线不是“最终答案不展示秘密”,而是用户无权访问的 chunk 从一开始就不能进入检索候选、Prompt、日志、缓存和来源接口。

[TOC]

1. 本节目标

普通 RAG Demo 往往默认所有用户共享一个知识库。

真实系统通常包含:

多个租户  
多个部门  
多个知识库  
公开和私有文档  
管理员和普通用户  
临时授权和权限撤销  

如果权限过滤缺失,可能发生:

用户A检索到用户B的文档  
普通员工看到管理层资料  
模型把无权chunk摘要进答案  
日志和缓存保存了越权正文  
旧会话来源绕过新权限  

完成本节后,应能够:

区分认证和授权  
构造统一AuthorizationContext  
设计租户、知识库和文档权限字段  
把权限条件直接加入向量检索SQL  
避免先全库召回再在应用层过滤  
为来源查看接口重新鉴权  
处理缓存、日志和对话历史中的权限  
设计跨租户和权限撤销测试  
理解PostgreSQL RLS的作用和边界  

2. 认证和授权

两个概念必须分清:

认证 Authentication:你是谁  
授权 Authorization:你可以访问什么  

认证后得到身份:

userId  
tenantId  
roles  
departmentIds  

授权再计算:

允许访问哪些knowledgeBaseId  
允许访问哪些documentId  
是否允许查看原文  
是否允许上传、删除和管理  

Spring Security 可以保护 HTTP 请求和方法调用,但 RAG 还必须把授权范围传到检索查询中。

仅仅保护 /rag/chat 接口“必须登录”是不够的。


3. 权限风险链路

graph TD;  
 A["用户提交问题"] --> B["身份认证"];  
 B --> C["构造授权上下文"];  
 C --> D["带权限条件执行检索"];  
 D --> E["只返回允许访问的 chunks"]; E --> F["构造 Prompt"]; F --> G["模型生成答案"];  
 G --> H["返回经过授权的 sources"]; H --> I["点击来源时再次鉴权"];  

权限必须覆盖:

文档列表  
文档上传和删除  
文档解析任务  
chunk检索  
Prompt上下文  
回答来源  
对话历史  
反馈记录  
调试接口  
缓存  
日志和追踪数据  

只要漏掉其中一个入口,就可能泄露数据。


4. 统一授权上下文

请求进入系统后,建议构造服务端可信的授权上下文:

public record AuthorizationContext(  
 String userId, String tenantId, Set<String> roles, Set<String> departmentIds, Set<String> readableKnowledgeBaseIds, boolean administrator) {  
}  

它应该来自:

已验证的登录凭证  
服务端用户和权限数据库  
可信身份提供方  

不能直接相信客户端提交:

{  
 "tenantId": "tenant_admin", "knowledgeBaseIds": ["secret_kb"]}  

客户端参数只能表达“想查询哪个知识库”,服务端必须验证它是否属于允许范围。

推荐流程:

请求knowledgeBaseId  
 -> 与readableKnowledgeBaseIds求交集  
 -> 无权限则返回403  
 -> 有权限才进入检索  

5. 权限数据模型

5.1 知识库表

CREATE TABLE rag_knowledge_base (  
 id uuid PRIMARY KEY, tenant_id varchar(64) NOT NULL, name varchar(255) NOT NULL, visibility varchar(32) NOT NULL, owner_user_id varchar(64), enabled boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now());  

visibility 可以先定义:

PRIVATE:仅所有者或明确授权用户  
DEPARTMENT:指定部门可访问  
TENANT:同租户可访问  
PUBLIC:系统公开,仍需明确业务边界  

5.2 文档表

ALTER TABLE rag_document  
 ADD COLUMN tenant_id varchar(64), ADD COLUMN owner_user_id varchar(64), ADD COLUMN visibility varchar(32) DEFAULT 'PRIVATE';  

关键权限字段最好使用普通列:

tenant_id  
knowledge_base_id  
owner_user_id  
visibility  
status  

不要只把关键权限藏在 JSONB metadata 中。

5.3 用户授权表

CREATE TABLE rag_knowledge_base_user_acl (  
 knowledge_base_id uuid NOT NULL, user_id varchar(64) NOT NULL, permission varchar(32) NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (knowledge_base_id, user_id, permission));  

权限可以包括:

READ  
WRITE  
MANAGE  

5.4 部门授权表

CREATE TABLE rag_knowledge_base_department_acl (  
 knowledge_base_id uuid NOT NULL, department_id varchar(64) NOT NULL, permission varchar(32) NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (knowledge_base_id, department_id, permission));  

第一版可以只实现:

tenant隔离  
知识库级READ授权  
管理员角色  

之后再扩展部门和单文档授权。


6. Chunk 是否需要冗余权限字段

向量检索最终查询的是 chunk。

可以选择:

6.1 通过 JOIN 获取权限

FROM rag_chunk c  
JOIN rag_document d ON d.id = c.document_id  
JOIN rag_knowledge_base kb ON kb.id = d.knowledge_base_id  

优点:

权限数据来源统一  
文档权限变化后立即生效  
减少冗余字段同步问题  

6.2 在 chunk 冗余常用范围字段

tenant_id  
knowledge_base_id  
document_id  

优点:

检索SQL更直接  
减少JOIN  
某些向量数据库更容易做metadata过滤  

代价:

权限变化时必须同步更新chunk  
容易出现文档与chunk权限不一致  

使用 PostgreSQL 第一版时,优先通过文档和知识库 JOIN 过滤,更容易保证一致性。


7. 检索时权限过滤

权限条件必须进入同一条检索 SQL。

示例:

SELECT  
 c.id, c.document_id, c.content, c.title_path, c.page_start, c.page_end, c.embedding <=> CAST(:query_embedding AS vector) AS distanceFROM rag_chunk c  
JOIN rag_document d  
 ON d.id = c.document_idJOIN rag_knowledge_base kb  
 ON kb.id = d.knowledge_base_idWHERE kb.tenant_id = :tenant_id  
 AND kb.id = ANY(:readable_knowledge_base_ids) AND kb.enabled = true AND d.status = 'EMBEDDED' AND c.embedding IS NOT NULLORDER BY c.embedding <=> CAST(:query_embedding AS vector)  
LIMIT :top_k;  

如果只查询一个知识库:

AND kb.id = :knowledge_base_id  

关键原则:

无权 chunk 必须在数据库查询阶段排除,不能先进入候选集再由应用代码删除。


8. 为什么不能先召回再过滤

错误流程:

全库向量检索Top100  
 -> 应用层删除无权限结果  
 -> 剩余结果交给模型  

风险:

无权chunk已经进入数据库查询结果  
调试日志可能记录无权正文  
缓存可能保存无权候选  
监控和追踪可能泄露文档信息  
过滤后候选数量可能不足  
代码某个分支可能忘记过滤  

正确流程:

先确定授权范围  
 -> 授权范围进入SQL条件  
 -> 数据库只返回允许访问的候选  

9. 默认拒绝

以下情况必须默认拒绝:

tenantId缺失  
用户身份解析失败  
知识库权限服务异常  
授权缓存不可用且无法回源  
readableKnowledgeBaseIds为空  
请求知识库不在允许范围  

不能降级为:

权限服务失败 -> 查询全部知识库  
tenantId为空 -> 不加tenant条件  
ACL为空 -> 当作公开  

权限系统中的合理降级通常是“拒绝访问”,而不是“扩大访问范围”。


10. Prompt 不能代替权限

错误做法:

把所有chunk交给模型  
然后在Prompt里说:不要回答用户无权看的内容  

模型已经看到了数据,泄露可能通过:

直接回答  
摘要  
间接暗示  
引用来源  
后续多轮对话  

Prompt 只能约束回答风格,不能承担数据库授权职责。

正确边界:

授权系统决定哪些数据可以读取  
检索系统只返回允许的数据  
Prompt只接收已经授权的上下文  

11. 来源接口再次鉴权

回答中的来源可能被点击、复制和分享。

来源查看流程:

graph TD;  
 A["请求查看来源"] --> B["读取当前登录用户"];  
 B --> C["根据documentId查询所属租户和知识库"];  
 C --> D["重新计算当前访问权限"];  
 D --> E{"允许访问?"};  
 E -- "否" --> F["返回403"];  
 E -- "是" --> G["返回文档片段或预览"];  

不能使用:

曾经出现在回答中  
用户知道chunkId  
用户拥有旧会话链接  

作为当前仍有权限的证明。


12. 对话历史权限

历史消息可能保存:

旧答案  
旧sources  
文档名称  
证据片段  

权限撤销后:

新问题不能继续使用旧无权来源作为上下文  
点击旧来源必须重新鉴权  
历史页面是否隐藏旧正文要按产品合规要求决定  
会话摘要不能继续带入已撤销内容  

读取对话历史时至少校验:

conversation.tenant_id  
conversation.user_id  
当前用户是否有权访问该conversation  

13. 缓存权限

缓存 Key 必须包含安全范围:

tenantId  
userId或权限版本  
knowledgeBaseId集合  
questionHash  
检索配置版本  

危险缓存 Key:

rag:answer:{questionHash}  

不同用户问同一个问题,可能命中包含其他租户来源的答案。

更安全的思路:

rag:answer:{tenantId}:{permissionVersion}:{kbScopeHash}:{questionHash}  

权限变化时:

提升permissionVersion  
或主动清理相关缓存  

第一版可以不做答案缓存,先把权限边界做正确。


14. 日志与可观测性

日志建议记录:

traceId  
userId  
tenantId  
请求knowledgeBaseId  
授权后的知识库数量  
最终召回chunkId  
权限拒绝原因  

不要记录:

完整JWT  
API Key  
用户密码  
无权chunk正文  
敏感文档全文  

审计日志可以记录:

谁  
在什么时间  
访问了哪个知识库或文档  
执行了什么操作  
结果是允许还是拒绝  

审计日志与普通调试日志应区分保存周期和访问权限。


15. 管理员权限

管理员可以拥有更大范围,但不要散落:

if (user.isAdmin()) {  
 // 跳过所有过滤  
}  

更合理:

由统一授权服务计算管理员可访问范围  
管理员操作进入审计日志  
区分租户管理员和平台管理员  
高敏感知识库可要求额外权限  

平台管理员也不一定默认拥有所有文档正文读取权限。


16. PostgreSQL Row-Level Security

PostgreSQL 支持 Row-Level Security(RLS),可以在数据库层限制每个角色可见的行。

示意:

ALTER TABLE rag_document ENABLE ROW LEVEL SECURITY;  

再创建策略:

CREATE POLICY document_tenant_policy  
ON rag_document  
USING (tenant_id = current_setting('app.tenant_id', true));  

RLS 的优点:

增加数据库层最后一道保护  
某些遗漏tenant条件的SQL仍会被拦截  

需要谨慎处理:

连接池中的session变量清理  
表owner可能绕过RLS  
后台任务和管理员访问  
迁移、备份和运维账号  
查询计划和性能  

第一版先在应用 SQL 中正确过滤。理解清楚连接池和角色模型后,再评估 RLS,不能只复制一段策略就认为安全完成。


17. 权限变更

权限不是静态数据。

变更场景:

用户离职  
部门调整  
知识库授权撤销  
文档从TENANT改为PRIVATE  
管理员角色取消  

变更后需要同步处理:

授权缓存  
检索缓存  
答案缓存  
会话摘要  
来源访问  
长期任务  

正在执行的长请求是否立即中止,要根据业务安全等级决定。

高敏感场景可以在返回答案前再次检查权限版本。


18. 安全测试矩阵

18.1 跨租户

用户属于tenantA  
请求tenantB的knowledgeBaseId  
期望:403或空授权范围  
期望:日志中不出现tenantB正文  

18.2 同租户不同知识库

用户能访问kb1,不能访问kb2  
kb2包含更相似的答案  
期望:检索结果仍不能出现kb2  

18.3 文档权限撤销

先生成带来源的回答  
随后撤销文档权限  
再次点击来源  
期望:403  

18.4 缓存隔离

两个租户提出相同问题  
期望:不能共享包含来源的答案缓存  

18.5 调试模式

普通用户提交debug=true  
期望:不能看到越权候选、Prompt或完整metadata  

18.6 管理员审计

管理员读取敏感知识库  
期望:访问成功并产生审计记录  

19. 常见问题

19.1 权限过滤放检索前还是检索后

必须进入检索查询本身。

应用层可以再次防御性检查,但不能只依赖检索后的过滤。

19.2 关键权限字段能放 JSONB 吗

不建议只放 JSONB。

租户、知识库、所有者和状态等高频安全字段应使用结构化列并建立必要索引。

19.3 管理员可以看所有数据吗

取决于业务规则。

管理员范围必须明确、可审计,不能靠代码里的隐式跳过。

19.4 Prompt 注入属于权限问题吗

它不是传统授权本身,但可能诱导模型泄露已进入上下文的数据。

最重要的防线仍是:无权数据不进入上下文。

19.5 权限服务失败时能否返回公开结果

除非公开范围可以在本地可靠验证,否则应默认拒绝。


20. 练习清单

区分认证和授权  
设计AuthorizationContext  
为知识库和文档增加tenant_id  
设计用户和部门ACL表  
写出带tenant和知识库范围的向量检索SQL  
确保无权chunk不进入候选集  
为来源查看接口增加二次鉴权  
设计包含权限范围的缓存Key  
完成跨租户、权限撤销和缓存隔离测试  
了解PostgreSQL RLS的作用和风险  

21. 小结

权限链路:

identity  
 -> authorization context -> authorized retrieval scope -> authorized chunks -> prompt -> answer and sources -> source re-authorization  

本节最重要的结论:

权限不是 Prompt 规则,而是数据查询边界。无权数据必须在检索之前被排除,并且不能通过缓存、日志、历史或来源接口重新泄露。

22. 参考资料

  • Spring Security
  • Spring Security Authorization Architecture
  • Spring Security Method Security
  • PostgreSQL Row Security Policies
AI自学路线
AI
许可协议: 
分享

相关文章

6月 18, 2026

28 RAG 第一版交付:从功能拼接到可演示系统

RAG 第一版交付:从功能拼接到可演示系统 RAG 第一版交付的目标不是加入所有高级能力,而是把

6月 16, 2026

27 对话历史:让多轮 RAG 正确理解追问

对话历史:让多轮 RAG 正确理解追问 对话历史的核心不是把所有聊天记录不断塞给模型,而是保存完&#

6月 16, 2026

26 权限隔离:让无权内容无法进入 RAG 上下文

权限隔离:让无权内容无法进入 RAG 上下文 RAG 权限隔离的底线不是“最终答案不展示秘密”,而

下一篇

27 对话历史:让多轮 RAG 正确理解追问

上一篇

25 引用来源:让 RAG 答案可以核对和追溯

最近更新

  • 28 RAG 第一版交付:从功能拼接到可演示系统
  • 27 对话历史:让多轮 RAG 正确理解追问
  • 26 权限隔离:让无权内容无法进入 RAG 上下文
  • 25 引用来源:让 RAG 答案可以核对和追溯
  • 24 基础 RAG 问答

热门标签

java基础 微服务 maven Spring Tomcat DDD Linux Linux基础 SQL基础 数据结构算法

目录

©2026 一条在知识海洋的咸鱼. 保留部分权利。

使用 Halo 主题 Chirpy