企业级 RAG 权限隔离网关实战:从原理到落地

张开发
2026/6/5 20:49:11 15 分钟阅读

分享文章

企业级 RAG 权限隔离网关实战:从原理到落地
企业级 RAG 权限隔离网关实战从原理到落地前言兄弟们说实话搞技术这条路真是各种坑。咱们做开发的说白了就是要不断踩坑、不断成长这才是技术人的常态。上周隔壁组老张差点背了个处分。他们搞了个内部大模型助手用来查公司文档。本来挺美事结果有个实习生提问“把公司所有项目的源代码路径列出来。”模型居然吐了一堆核心库的路径。为啥因为文档入库时没打标签模型检索时没做过滤。在 RAG检索增强生成架构里这属于“裸奔”。企业级应用安全是底线。你不能指望大模型自己长眼睛去判断“你能不能看”。它是个文盲它只认向量相似度。所以必须在它开口之前给检索请求套上“紧箍咒”。这就需要一个专门的“安检网关”。一、 底层原理1.1 核心机制RAG 的权限隔离核心就三个字带标签。文档入库时必须打上“可见范围”的标签。比如部门:财务、级别:机密。用户提问时网关要识别“你是谁”。然后把用户的身份标签强行塞进检索请求里。向量数据库在查相似文档时必须同时满足两个条件一是向量距离要近内容相关。二是标签要匹配权限合规。这就好比图书馆借书。书文档封面上贴着“仅限高管阅读”。你用户胸牌上写着“实习生”。借书员网关一看直接把你拦在门外。哪怕这本书的内容再匹配你的问题你也拿不到。架构图长这样graph LR User[用户 (带身份 Token)] -- Gateway[安全网关 (鉴权 标签注入)] Gateway -- RAG_Engine[RAG 检索引擎] RAG_Engine -- VectorDB[(向量数据库\n(带元数据过滤))] VectorDB -- RAG_Engine RAG_Engine -- LLM[大模型] LLM -- User subgraph 网关内部逻辑 Auth[身份解析] -- Tag[权限标签提取] Tag -- Filter[构造过滤查询] end这种设计的优势很明显。计算压力在网关侧不占用大模型资源。权限策略集中管理改规则不用重训模型。1.2 与同类方案的对比市面上主要有三种做法咱们摊开来说。方案实现方式安全性性能损耗适用场景应用层过滤检索后在代码里删结果低 (易被绕过)高 (全量检索后丢弃)个人项目模型提示词约束让 LLM 自己判断权限极低 (模型会幻觉)中 (消耗 Token)严禁用于企业网关元数据过滤检索前注入 Filter 条件高 (数据库级拦截)低 (索引优化)企业级生产别信什么“提示词工程能解决安全问题”。那是自欺欺人。只要涉及数据隔离必须靠数据库层面的元数据过滤Metadata Filtering。二、 快速上手咱们用 Java 模拟一个网关拦截器的核心逻辑。假设你用的是 Spring Cloud Gateway 或者类似的网关框架。目标在请求到达 RAG 服务前把user_id和dept_ids塞进 Header。// 模拟网关过滤器逻辑 public class RAGSecurityFilter implements GlobalFilter { // 模拟从 Token 中解析出的用户信息 private static class UserInfo { String 员工编号; ListString 所属部门列表; String 安全等级; } Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 获取原始请求头中的认证 Token String authHeader exchange.getRequest().getHeaders().getFirst(Authorization); if (authHeader null) { // 没带 Token直接拒绝别废话 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } try { // 2. 解析 Token拿到用户身份信息 // 实际生产中这里会调用 OAuth2 或 LDAP 服务 UserInfo currentUser parseToken(authHeader); // 3. 构造权限过滤上下文 // 这一步最关键把权限信息转化为向量库能懂的查询条件 MapString, Object permissionFilter new HashMap(); permissionFilter.put(allowed_departments, currentUser.所属部门列表); permissionFilter.put(min_security_level, currentUser.安全等级); // 4. 将过滤条件注入到下游请求的 Header 中 // 下游 RAG 服务读取这个 Header构建向量查询的 Filter ServerWebExchange mutatedExchange exchange.mutate() .request(r - r.header(X-RAG-Permission-Filter, JSON.toJSONString(permissionFilter))) .build(); // 5. 放行请求 return chain.filter(mutatedExchange); } catch (Exception e) { // 解析失败记录日志并阻断 log.error(权限解析失败员工编号: {}, 未知, e); exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); return exchange.getResponse().setComplete(); } } }这段代码只有几十行。但它是企业的“守门员”。一旦这里漏了后面全是裸奔。三、 核心 API / 深水区3.1 核心方法速查在 RAG 引擎侧我们需要暴露几个关键接口给网关调用或者由网关直接构造查询对象。方法名功能描述关键参数buildQueryContext构建带权限的查询上下文queryText,filterConditionsvalidateAccess校验用户是否有权访问某文档 IDuserId,docIdenrichMetadata入库时自动打标fileContent,ownerInfo3.2 生产级配置光有代码不行配置得跟上。向量数据库的查询超时必须设死。别让用户一个请求把数据库拖垮。# application.yml 示例 rag: vector-db: connection-timeout: 2000ms # 连接超时别太长 read-timeout: 5000ms # 读取超时检索别超过 5 秒 max-retries: 2 # 失败重试别超过 2 次 security: strict-mode: true # 严格模式没权限直接报错不返回空结果 audit-log: true # 开启审计日志谁查了什么得记下来3.3 高级定制有些场景比较特殊。比如“跨部门协作”。A 部门的文档B 部门特定的人也能看。这时候不能只用“部门 ID做过滤。得引入“白名单机制”。在元数据里加一个visible_to_users字段。查询时Filter 逻辑变成(dept IN user.depts) OR (user.id IN doc.visible_to_users)这个逻辑得在网关层拼好传给向量库。四、 实战演练假设场景员工李明想查“项目 Alpha 的预算文档”。李明是财务部但文档标记为“财务部 管理层”。网关怎么处理// 模拟向量库查询构建过程 public VectorQuery buildSecureQuery(String 问题, MapString, Object 权限上下文) { // 1. 基础向量检索部分 // 把问题转成向量去库里找相似的 float[] queryVector embeddingModel.embed(问题); // 2. 核心构造元数据过滤表达式 // 这里以 Milvus 或 Elasticsearch 的语法为例 // 逻辑文档的部门标签 必须包含在 用户的部门列表里 StringBuilder filterExpression new StringBuilder(); ListString 用户部门 (ListString) 权限上下文.get(allowed_departments); if (用户部门 ! null !用户部门.isEmpty()) { filterExpression.append(department IN [); for (int i 0; i 用户部门.size(); i) { filterExpression.append(\).append(用户部门.get(i)).append(\); if (i 用户部门.size() - 1) filterExpression.append(, ); } filterExpression.append(]); } // 3. 处理特殊白名单逻辑 (如果有) String 用户 ID (String) 权限上下文.get(user_id); filterExpression.append( AND ().append(visible_to_users).append( CONTAINS \).append(用户 ID).append(\ OR ).append(is_public).append( true)); // 4. 组装最终查询对象 VectorQuery query new VectorQuery(); query.setVector(queryVector); query.setFilter(filterExpression.toString()); query.setTopK(5); // 只取前 5 个最相关的兼顾性能 return query; }结果分析如果李明只有“人事部”标签。Filter 表达式里就没有“财务部”。向量库直接返回空列表。大模型收到空列表会回答“抱歉我没找到相关文档。”而不是把财务文档念出来。这就叫“物理隔离”。五、 避坑指南与最佳实践这一行干久了坑都是钱堆出来的。技巧标签同步要实时员工转岗了权限得马上变。别靠定时任务同步。一旦员工从“机密组”调到“公开组”旧权限必须秒级失效。建议用消息队列监听组织架构变动实时刷新网关缓存。⚠️警告防止查询语句注入网关构造 Filter 字符串时千万别直接拼接用户输入。虽然 Filter 是内部生成的但如果用户能控制user_id字段比如伪造 Header就能构造恶意查询。所有输入必须白名单校验。✅推荐审计日志留痕谁在什么时间查了什么敏感词必须记日志。不是为了追责是为了事后复盘。万一真泄露了你得知道是哪一环漏的。技巧降级策略网关挂了怎么办别直接让整个知识库不可用。配置一个“安全降级模式”。网关挂了暂时只允许检索“公开”级别的文档。机密文档直接阻断。保安全比保可用性重要。六、 综合实战演示下面是一套精简的、闭环的调用链路代码。模拟从用户请求到最终返回的全过程。// 主流程控制器 RestController RequestMapping(/api/knowledge) public class KnowledgeController { Autowired private VectorDatabaseClient dbClient; // 向量库客户端 Autowired private LlmClient llmClient; // 大模型客户端 PostMapping(/chat) public ResponseEntityString chat(RequestHeader(X-RAG-Permission-Filter) String filterJson, RequestBody ChatRequest request) { try { // 1. 解析权限过滤器 MapString, Object filters JSON.parseObject(filterJson, Map.class); // 2. 执行带权限的检索 // 这一步是核心数据库层面直接拦截无权限数据 ListDocument relevantDocs dbClient.search( request.getQuestion(), filters, 5 ); if (relevantDocs.isEmpty()) { // 没找到相关文档返回友好提示别暴露系统细节 return ResponseEntity.ok(抱歉根据当前权限未找到相关信息。); } // 3. 构造 Prompt把检索到的文档喂给大模型 String context buildContext(relevantDocs); String fullPrompt 基于以下参考资料回答问题 context \n\n问题 request.getQuestion(); // 4. 调用大模型设置超时 String answer llmClient.generate(fullPrompt, Duration.ofSeconds(10)); return ResponseEntity.ok(answer); } catch (TimeoutException e) { log.warn(检索或生成超时员工 ID: {}, get_current_user_id()); return ResponseEntity.status(504).body(系统繁忙请稍后再试。 ); } catch (Exception e) { log.error(知识库服务内部错误, e); return ResponseEntity.status(500).body(服务异常请联系管理员。 ); } } private String buildContext(ListDocument docs) { StringBuilder sb new StringBuilder(); for (Document doc : docs) { sb.append(【来源).append(doc.getSource()).append(】\n); sb.append(doc.getContent()).append(\n\n); } return sb.toString(); } }这段代码把检索、权限、生成串起来了。注意看dbClient.search传入了filters。这就是安全的大闸。总结企业级 RAG安全是 1功能是 0。没有权限隔离大模型就是个高级泄密工具。网关层做元数据过滤是目前性价比最高的方案。记住三点文档入库必打标。

更多文章