AI 助手 02:先固定请求上下文,再谈知识库权限

记录 V1 如何在 Java 与 Python 服务之间传递 requestId、租户、用户和角色,并把开发便利与生产边界分开。

字数 1070 阅读时长 ≈ 4 分钟 2026-7-16 2026-7-27
所属项目:AI-assistant
AI 助手 02:先固定请求上下文,再谈知识库权限

做企业 AI 应用时,最危险的错误往往不是模型回答错了,而是模型回答了本不该被这个人看到的内容。因此在接入 RAG 之前,这次迭代先把每一次请求的身份边界固定下来:请求 ID、租户 ID、用户 ID 和角色。

这不是认证系统的替代品。当前 V1 还没有接入正式登录,它做的是让业务代码从第一天起就不依赖“默认就是同一个用户”的假设。以后无论是会话查询、文档检索还是工具调用,都有可以沿用的上下文入口。

先回答一个问题:上下文从哪里来

浏览器不应该直接决定自己属于哪个租户、拥有哪些角色。正式环境里,这些信息应由网关或认证服务根据令牌校验结果注入;当前 V1 的 Java 服务先约定四个请求头:

x-request-id
x-tenant-id
x-user-id
x-user-roles

其中 x-request-id 用于跨服务追踪;其余三个是数据隔离和授权判断的输入。Java 的 RequestContextFilter 在每个请求进入时解析它们,写入 RequestContextHolder;响应也带回同一个 request ID,方便前端、日志和调用记录串到一起。

请求上下文从浏览器到 Java、Python 和数据库调用日志的传递过程

开发模式可以省事,但不能把省事带到生产

本地调接口时,要求每次手动填租户、用户和角色会拖慢验证,所以配置提供了开发默认值:

app:
  request-context:
    development-mode: true
    default-tenant-id: 1
    default-user-id: 1
    default-user-roles: ADMIN

但当 REQUEST_CONTEXT_DEVELOPMENT_MODE=false 时,缺少任意主体信息、传入非正整数 ID,或角色为空,过滤器会直接返回 400 REQUEST_CONTEXT_INVALID,不会进入 Controller。

这个取舍很朴素:开发环境为了可运行性允许“demo 身份”,生产环境为了隔离性要求“已验证身份”。不要在业务查询里再写一套“没有 tenantId 就查默认租户”的兜底,这种兜底往往只会在上线后变成越权入口。

Java 与 Python 各自只做该做的事

Java 负责把上下文作为业务约束使用。比如查询会话时,条件同时包含 tenant_iduser_idsession_id;创建消息和调用日志时,也写入同一个租户、用户与 request ID。这样即使有人猜到另一个会话 ID,也无法仅靠 ID 读取对方的历史消息。

调用 Python AI 服务时,Java 会显式转发四个头。Python 使用 ContextVar 保存异步请求范围内的上下文,并把 request ID 回写到响应;它目前不做会话归属校验,也不直接写业务数据。这能避免两个服务同时维护授权规则,后续 Python 做检索时则可以把 tenant、user、roles 当作检索过滤条件,而不是从提示词里解析“你是谁”。

清理上下文不是小细节

Java 的 RequestContextHolder 使用 ThreadLocal,Python 使用 ContextVar。它们都必须在请求结束后清理:线程池线程和异步执行上下文会被复用,若上一个请求的上下文残留,就可能把 A 用户的身份带入 B 用户的请求。

当前两个服务都在 finally 中执行清理或重置。Java 的 SSE 路径也会在异步任务开始时重新设置上下文,并在完成后清除。这个动作并不等于已经解决了所有异步安全问题,但它至少把“上下文生命周期”从隐含假设变成了明确代码。

如何验收这个边界

当前可以通过以下接口观察开发默认上下文:

Invoke-RestMethod http://localhost:8080/api/context

并在关闭开发模式后,用缺失主体头的请求验证 REQUEST_CONTEXT_INVALID。现有单元测试也覆盖了两件事:开发默认值和自动生成 request ID 是否可用;非开发模式是否会阻断缺失主体信息的请求。测试结束后还会断言 Java 上下文已被清理。

下一步接入文档知识库时,这套上下文不会自动带来权限控制。还需要明确文档、分段和向量的授权模型,并确保检索条件在向量召回前生效。不过至少从这一轮开始,系统已经有了问这个问题的能力:这条内容究竟能不能给当前请求里的这个人看?