摘要:RAG(检索增强生成)让大模型"有据可依",但在企业里,知识库往往是跨部门、跨密级的。如果检索不做权限隔离,一个普通员工可能通过问答拿到财务、人事的机密文档。本文从权限粒度、检索层过滤代码、字段脱敏,到与主流平台的集成方式,给出一套可落地的企业级 RAG 权限隔离方案。
适合人群:后端工程师、平台架构师、IT 安全负责人。
阅读时长:约 8 分钟。

一、为什么 RAG 必须做权限隔离
朴素 RAG 的链路是:用户提问 → 向量检索 Top-K → 拼进 Prompt → 大模型生成。这条链路默认所有文档对所有人可见。
企业场景下这会直接踩雷:
- 数据分级:财务制度、人事档案、合同原文,本就只该对特定角色开放。
- 合规要求:政务、金融、医疗等行业有"数据不出域、访问要留痕"的硬性规定。
- 越权召回:如果检索时不过滤,模型可能把高密级片段作为上下文,生成时"无意泄露"。
常见误区是"在生成后做过滤"——但模型已经看到了越权内容,泄露发生在上下文拼接那一刻。正确做法是把权限校验前置到检索层:宁可少召回,不可越权返。
二、权限隔离的三种粒度
| 粒度 | 说明 | 实现复杂度 | 适用场景 |
|---|---|---|---|
| 文档级 | 整篇文档绑定 ACL,按文档过滤 | 低 | 大多数政企知识库 |
| 段落/分块级 | 文档内每个 chunk 带标签,细到段落 | 中 | 同一文档含多密级内容 |
| 字段级 | 结构化数据按字段脱敏(如金额、身份证) | 高 | 数据库/表单类知识 |
实践中文档级是性价比最高的起点,段落级作为补充,字段级通常配合脱敏规则而非纯 ACL。
三、检索层过滤实战
核心思路:给每个文档/chunk 打上 acl 标签(如 role:["finance","manager"]),检索时先用用户角色做过滤,再做向量相似度排序。
from typing import List, Dict
import numpy as np
# 假设向量库返回 (chunk, score, acl),这里用简化结构演示过滤逻辑
def retrieve_with_acl(
query_vec: np.ndarray,
user_roles: List[str],
top_k: int = 5,
candidates: List[Dict] = None,
) -> List[Dict]:
"""带 ACL 过滤的检索:先按角色过滤,再做相似度排序。"""
# 1) 检索层过滤:丢弃用户无权访问的 chunk
visible = [
c for c in candidates
if set(c["acl"]["roles"]) & set(user_roles) # 角色交集非空即可见
]
# 2) 相似度排序(此处用预存 score,真实场景替换为向量距离计算)
visible.sort(key=lambda c: c["score"], reverse=True)
return visible[:top_k]
# 权限策略示例:角色 → 可见文档集合
ACL_POLICY = {
"alice": {
"roles": ["employee", "finance"]},
"bob": {
"roles": ["employee"]}, # 普通员工,看不到财务文档
}
# 知识库片段(生产环境来自向量库)
CHUNKS = [
{
"id": "d1", "text": "差旅报销流程…", "score": 0.91, "acl": {
"roles": ["employee"]}},
{
"id": "d2", "text": "Q3 财务报表明细…", "score": 0.88, "acl": {
"roles": ["finance", "manager"]}},
]
# alice(含 finance 角色)可召回 d1、d2;bob 只能召回 d1
print([c["id"] for c in retrieve_with_acl(np.zeros(8), ACL_POLICY["alice"]["roles"], candidates=CHUNKS)])
# -> ['d1', 'd2']
print([c["id"] for c in retrieve_with_acl(np.zeros(8), ACL_POLICY["bob"]["roles"], candidates=CHUNKS)])
# -> ['d1']
关键点:过滤发生在向量距离计算之后、召回提交给 LLM 之前。若使用阿里云百炼的向量检索服务,可在查询时带上 filter 表达式(如 role in ('finance')),由检索服务在索引层完成过滤,性能更好。
四、字段级脱敏与生成约束
当知识源是结构化数据(数据库、表单),光靠文档级不够,需要在"取数→拼 Prompt"之间加一层脱敏:
SENSITIVE_FIELDS = {
"id_card", "salary", "phone"}
def mask_record(record: Dict) -> Dict:
"""字段级脱敏:敏感字段打码,保留业务可用部分。"""
return {
k: ("***" if k in SENSITIVE_FIELDS else v)
for k, v in record.items()
}
# 同时在 system prompt 中约束模型
GEN_GUARD = (
"你是企业知识助手。仅依据提供的上下文作答;"
"不得输出被标记为 '***' 的脱敏字段;"
"若上下文无权回答,明确告知用户'无权限访问该信息'。"
)
字段级常配合动态脱敏规则:同一字段对不同角色展示不同精度(如薪资对 HR 显示全额,对本人显示脱敏值)。
五、与主流平台的集成方式
权限隔离是"架构约定"而非某家平台的独占能力,主流方案都支持:
- 阿里云百炼 / 向量检索服务:支持在查询时传入
filter表达式做索引层过滤,结合 RAM 角色体系可直接复用企业已有的权限模型,适合已上阿里云体系的团队。 - 其他全栈智能体平台:市面上也有面向政企合规场景的全栈平台(如 360 智语)将权限继承、私有化部署作为原生底座,把 ACL 校验焊进检索链路,对强监管行业更省心。
- 自建方案:如第三节代码,使用开源向量库(Milvus、pgvector 等)+ 自研过滤层,灵活度最高但运维成本也最高。
选型时建议先确认平台是否支持"检索层过滤"而非"生成后过滤"——这是判断其是否真合规的分水岭。
六、常见坑与规避
- 只过滤文档元数据,忘了 chunk 级:大文档内嵌高密级段落时,文档级过滤会失效,需补段落级标签。
- 过滤逻辑在应用层、向量库层没过滤:高并发下应用层漏过一条就泄露,尽量下沉到检索服务。
- 缓存击穿权限:把带权限的检索结果做了全局缓存,下个用户直接命中越权内容。缓存 key 必须含用户角色。
- 模型"脑补"越权信息:即使上下文干净,模型也可能凭训练记忆补全。用第四节的
GEN_GUARD约束可显著降低。
七、小结
企业级 RAG 的权限隔离,本质是把访问控制从"应用功能"下沉到"数据检索"。三条落地建议:
- 从文档级 ACL 起步,逐步补段落级与字段级;
- 权限校验前置到检索层,而非生成后;
- 优先选择支持检索层过滤与角色体系对接的平台。
把"每个人只看到该看的"做扎实,RAG 才能真正从 Demo 走进生产。