news 2026/10/15 4:08:25

【Agent与工作流实战】如何避免不同用户的信息混在一起:设计会话标识与数据隔离

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Agent与工作流实战】如何避免不同用户的信息混在一起:设计会话标识与数据隔离

AI核心能力提升课程:
云博士带小白:自然语言AI编程做A股选股产品
https://www.bilibili.com/cheese/play/ss142425888

AI神经网络全谱系架构全景精讲
https://www.bilibili.com/cheese/play/ss743886858

哈佛博后带你玩转深度学习实战
https://www.bilibili.com/cheese/play/ss512662943

如何避免不同用户的信息混在一起:设计会话标识与数据隔离

一次普通的订单查询,为什么可能变成数据泄露?

假设你正在开发一个电商客服助手。用户甲询问“我的订单到哪里了”,助手查询订单后回答:“订单 A1001 已发货,预计明天送达。”随后,用户乙打开自己的聊天窗口,也问了同样的问题,助手却返回了甲的订单信息。

问题不一定出在模型本身。更常见的原因是:系统没有明确区分不同用户的会话,或者虽然区分了聊天记录,却没有在查询订单时校验用户身份。

这类错误通常发生在几个环节:

  • 多个用户共用同一个会话标识,导致历史消息被错误复用。
  • 服务端把上一次请求的用户信息保存在共享变量中,并发请求相互覆盖。
  • 系统根据客户端传入的user_id查询数据,却没有验证这个用户是否有权读取对应订单。
  • 会话记录隔离了,但缓存、数据库查询或检索结果没有隔离。
  • 用户退出登录后,旧会话仍能被其他登录身份继续使用。

要解决这些问题,不能只给每个聊天窗口生成一个随机 ID,而应该建立完整的身份与数据隔离规则。

本文用一个虚构的电商订单助手,搭建一套可独立运行的 Python 最小示例,说明如何设计用户标识、会话标识、权限校验和会话存储,并通过正常、边界、失败及越权测试检查隔离是否有效。

一、先分清用户、会话和请求

在设计数据结构之前,需要先明确三个标识分别代表什么。

标识示例用途生命周期
user_iduser_100表示系统识别并认证的用户主体通常跨多个会话存在
conversation_idconv_a81f...表示某个用户的一段对话创建新会话时生成,可按业务规则结束
request_idreq_9c20...标识一次请求,方便日志追踪和排查每次请求不同

它们不能互相替代。

user_id解决“是谁”,conversation_id解决“正在进行哪段对话”,request_id解决“这是哪一次请求”。

例如,同一个用户可以有两个独立会话:

user_100 ├── conv_aaa:查询订单 └── conv_bbb:咨询退货政策

另一个用户即使发出完全相同的问题,也应该使用自己的会话:

user_200 └── conv_ccc:查询订单

需要特别注意:会话 ID 不是身份凭证。即使conversation_id足够随机,也不能因为请求方知道这个 ID,就允许它访问对应数据。

1. 身份由认证层确定,而不是由请求参数自证

如果客户端发送:

{ "user_id": "user_100", "conversation_id": "conv_aaa", "question": "我的订单到哪里了?" }

服务端不能直接相信请求中的user_id。攻击者可以把它改成user_200,尝试读取另一个用户的信息。

真实系统中,user_id应由服务端从已经验证的登录会话、令牌或其他可信认证上下文中取得。客户端提供的用户 ID 最多作为业务参数,不能单独证明用户身份。

为了让后续示例能够独立运行,本文会用一个固定的演示身份模拟认证层。它只用于理解隔离规则,不代表真实登录系统,也不能直接替代生产环境的认证机制。

2. 会话归属必须由服务端验证

每个会话都应该有明确的所有者:

conversation_id → owner_user_id

当用户访问会话时,服务端至少需要检查:

会话存在 AND 会话所有者 == 当前已认证用户

任一条件不满足,都不能读取或继续使用该会话的历史消息。

3. 读取历史消息和读取业务数据是两道不同的权限检查

假设用户甲拥有会话conv_aaa,但订单A1001属于用户甲,订单B2002属于用户乙。

即使用户甲合法访问自己的会话,也不意味着他可以读取任意订单。系统必须分别检查:

  1. 用户甲能否访问conv_aaa。
  2. 用户甲能否读取A1001或B2002。

第一道检查保护会话历史,第二道检查保护业务数据。只做好其中一道是不够的。

二、设计最小隔离方案

本例选择 Python 3 和 SQLite。SQLite 是轻量级关系数据库,适合演示数据模型、归属校验和查询约束,不需要部署独立数据库服务。

适用环境:

  • Python 3.10 或更高版本。
  • 使用 Python 标准库,不需要安装第三方依赖。
  • 数据库使用内存模式,程序退出后演示数据即消失。
  • 用户身份通过参数模拟,真实身份认证不在本例范围内。

1. 设计数据结构

我们只需要三张表:

表名关键字段作用
conversationsconversation_id、owner_user_id保存会话及所有者
messagesconversation_id、role、content保存对话消息
ordersorder_id、owner_user_id、status保存订单及订单所有者

其中最重要的关系是:

conversations.owner_user_id └── 决定谁可以访问会话 messages.conversation_id └── 决定消息属于哪段会话 orders.owner_user_id └── 决定谁可以访问订单

消息表不必在每条消息中重复保存用户 ID,因为消息通过conversation_id关联到会话,会话再记录所有者。但查询消息前仍然必须检查会话归属,不能只拿到会话 ID 就直接返回历史内容。

2. 明确访问规则

本例采用以下规则:

  1. 创建会话时,由服务端生成新的会话 ID,并绑定当前已认证用户。
  2. 读取历史记录时,先验证会话归属,再读取消息。
  3. 写入消息时,同样验证会话归属,不能只在读取时做权限检查。
  4. 查询订单时,查询条件必须同时包含订单 ID 和当前用户 ID。
  5. 找不到会话、用户身份缺失或权限不符时,拒绝操作。
  6. 不把会话 ID 的随机性当作权限控制。

这种设计遵循默认拒绝和每次请求都验证权限的原则。OWASP 的授权安全建议也强调,认证成功不等于拥有所有资源的访问权,随机化资源标识不能代替授权检查。[1]

三、完整实现:一个带隔离检查的订单助手存储层

这个最小实现不接入大模型,而是先把最容易导致信息串线的底层问题做好:会话创建、消息读写和订单查询。

这不是刻意省略智能体核心,而是把身份与数据隔离独立出来验证。无论上层使用哪一种模型或 Agent 框架,底层都需要类似的约束。

1. 文件清单

文件用途
app.py初始化演示数据库、实现会话与订单访问控制,并运行测试

全部依赖来自 Python 标准库:

  • sqlite3:操作 SQLite 数据库。
  • secrets:生成不可预测的会话标识。
  • unittest:运行自动化验收测试。

Python 的secrets模块适合生成难以猜测的随机令牌;不过,难以猜测只是降低被猜中的风险,不代表获得该令牌的人就自动拥有资源访问权限。[2]

2.app.py完整代码

将以下内容完整保存为app.py。

import secretsimport sqlite3import unittestclass AccessDenied(Exception): """请求方没有访问资源的权限。"""class NotFound(Exception): """资源不存在,或对当前用户不可见。"""class OrderAssistantStore: """ 最小演示实现。 authenticated_user_id 必须由可信认证层提供。 本示例的测试会直接传入虚构用户 ID, 不能将这种方式当作生产环境的登录认证。 """ def __init__(self): self.db = sqlite3.connect(":memory:") self.db.row_factory = sqlite3.Row self._init_db() def _init_db(self): self.db.executescript(""" PRAGMA foreign_keys = ON; CREATE TABLE conversations ( conversation_id TEXT PRIMARY KEY, owner_user_id TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP

这里的 SQL 使用参数绑定,而不是把用户输入拼接进 SQL 字符串。Python 的sqlite3文档明确建议使用占位符绑定参数,以降低 SQL 注入风险。[3]

3. 运行测试

在保存了app.py的目录中,使用终端执行以下命令。下面以 Bash 为例:

python --version python -m py_compile app.py python app.py

不需要安装第三方依赖,也不需要配置密钥或模型端点。

测试使用unittest的断言检查实际返回行为。预期结果是六项测试全部通过;这并不意味着真实登录、网络接口或生产数据库已经通过验证。

四、从代码中提炼出可复用的隔离流程

这段实现可以概括为以下处理链路:

#chatgpt-mermaid-_r_1t1_{font-family:-apple-system-body,ui-sans-serif,-apple-system,system-ui,“Segoe UI”,Helvetica,“Apple Color Emoji”,Arial,sans-serif,“Segoe UI Emoji”,“Segoe UI Symbol”;font-size:16px;fill:rgb(13, 13, 13);}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#chatgpt-mermaid-_r_1t1_ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#chatgpt-mermaid-_r_1t1_ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#chatgpt-mermaid-_r_1t1_ .error-icon{fill:rgb(243, 243, 243);}#chatgpt-mermaid-_r_1t1_ .error-text{fill:rgb(13, 13, 13);stroke:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ .edge-thickness-normal{stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ .edge-thickness-thick{stroke-width:3.5px;}#chatgpt-mermaid-_r_1t1_ .edge-pattern-solid{stroke-dasharray:0;}#chatgpt-mermaid-_r_1t1_ .edge-thickness-invisible{stroke-width:0;fill:none;}#chatgpt-mermaid-_r_1t1_ .edge-pattern-dashed{stroke-dasharray:3;}#chatgpt-mermaid-_r_1t1_ .edge-pattern-dotted{stroke-dasharray:2;}#chatgpt-mermaid-_r_1t1_ .marker{fill:rgb(143, 143, 143);stroke:rgb(143, 143, 143);}#chatgpt-mermaid-_r_1t1_ .marker.cross{stroke:rgb(143, 143, 143);}#chatgpt-mermaid-_r_1t1_ svg{font-family:-apple-system-body,ui-sans-serif,-apple-system,system-ui,“Segoe UI”,Helvetica,“Apple Color Emoji”,Arial,sans-serif,“Segoe UI Emoji”,“Segoe UI Symbol”;font-size:16px;}#chatgpt-mermaid-_r_1t1_ p{margin:0;}#chatgpt-mermaid-_r_1t1_ .label{font-family:-apple-system-body,ui-sans-serif,-apple-system,system-ui,“Segoe UI”,Helvetica,“Apple Color Emoji”,Arial,sans-serif,“Segoe UI Emoji”,“Segoe UI Symbol”;color:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ .cluster-label text{fill:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ .cluster-label span{color:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ .cluster-label span p{background-color:transparent;}#chatgpt-mermaid-_r_1t1_ .label text,#chatgpt-mermaid-_r_1t1_ span{fill:rgb(13, 13, 13);color:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ .node rect,#chatgpt-mermaid-_r_1t1_ .node circle,#chatgpt-mermaid-_r_1t1_ .node ellipse,#chatgpt-mermaid-_r_1t1_ .node polygon,#chatgpt-mermaid-_r_1t1_ .node path{fill:rgb(222, 234, 251);stroke:rgb(83, 154, 248);stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ .rough-node .label text,#chatgpt-mermaid-_r_1t1_ .node .label text,#chatgpt-mermaid-_r_1t1_ .image-shape .label,#chatgpt-mermaid-_r_1t1_ .icon-shape .label{text-anchor:middle;}#chatgpt-mermaid-_r_1t1_ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ .rough-node .label,#chatgpt-mermaid-_r_1t1_ .node .label,#chatgpt-mermaid-_r_1t1_ .image-shape .label,#chatgpt-mermaid-_r_1t1_ .icon-shape .label{text-align:center;}#chatgpt-mermaid-_r_1t1_ .node.clickable{cursor:pointer;}#chatgpt-mermaid-_r_1t1_ .root .anchor path{fill:rgb(143, 143, 143)!important;stroke-width:0;stroke:rgb(143, 143, 143);}#chatgpt-mermaid-_r_1t1_ .arrowheadPath{fill:rgb(143, 143, 143);}#chatgpt-mermaid-_r_1t1_ .edgePath .path{stroke:rgb(143, 143, 143);stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ .flowchart-link{stroke:rgb(143, 143, 143);fill:none;}#chatgpt-mermaid-_r_1t1_ .edgeLabel{background-color:rgb(252, 252, 252);text-align:center;}#chatgpt-mermaid-_r_1t1_ .edgeLabel p{background-color:rgb(252, 252, 252);}#chatgpt-mermaid-_r_1t1_ .edgeLabel rect{opacity:0.5;background-color:rgb(252, 252, 252);fill:rgb(252, 252, 252);}#chatgpt-mermaid-_r_1t1_ .labelBkg{background-color:rgba(252, 252, 252, 0.5);}#chatgpt-mermaid-_r_1t1_ .cluster rect{fill:rgb(243, 243, 243);stroke:rgba(0, 0, 0, 0.1);stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ .cluster text{fill:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ .cluster span{color:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:-apple-system-body,ui-sans-serif,-apple-system,system-ui,“Segoe UI”,Helvetica,“Apple Color Emoji”,Arial,sans-serif,“Segoe UI Emoji”,“Segoe UI Symbol”;font-size:12px;background:rgb(243, 243, 243);border:1px solid rgba(0, 0, 0, 0.1);border-radius:2px;pointer-events:none;z-index:100;}#chatgpt-mermaid-_r_1t1_ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:rgb(13, 13, 13);}#chatgpt-mermaid-_r_1t1_ rect.text{fill:none;stroke-width:0;}#chatgpt-mermaid-_r_1t1_ .icon-shape,#chatgpt-mermaid-_r_1t1_ .image-shape{background-color:rgb(252, 252, 252);text-align:center;}#chatgpt-mermaid-_r_1t1_ .icon-shape p,#chatgpt-mermaid-_r_1t1_ .image-shape p{background-color:rgb(252, 252, 252);padding:2px;}#chatgpt-mermaid-_r_1t1_ .icon-shape .label rect,#chatgpt-mermaid-_r_1t1_ .image-shape .label rect{opacity:0.5;background-color:rgb(252, 252, 252);fill:rgb(252, 252, 252);}#chatgpt-mermaid-_r_1t1_ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#chatgpt-mermaid-_r_1t1_ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#chatgpt-mermaid-_r_1t1_ .node .neo-node{stroke:rgb(83, 154, 248);}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].node rect,#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].cluster rect,#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].node polygon{stroke:url(#chatgpt-mermaid-_r_1t1_-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].swimlane.cluster rect{filter:none;}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].node path{stroke:url(#chatgpt-mermaid-_r_1t1_-gradient);stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].node .neo-line path{stroke:rgb(83, 154, 248);filter:none;}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].node circle{stroke:url(#chatgpt-mermaid-_r_1t1_-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].node circle .state-start{fill:#000000;}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].icon-shape .icon{fill:url(#chatgpt-mermaid-_r_1t1_-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#chatgpt-mermaid-_r_1t1_ [data-look=“neo”].icon-shape .icon-neo path{stroke:url(#chatgpt-mermaid-_r_1t1_-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#chatgpt-mermaid-_r_1t1_ .node text{font-size:14px;font-weight:600;letter-spacing:normal;fill:rgb(0, 79, 153);}#chatgpt-mermaid-_r_1t1_ .edgeLabels text{font-size:13px;font-weight:600;letter-spacing:-0.08px;fill:rgb(0, 79, 153);}#chatgpt-mermaid-_r_1t1_ .node tspan[font-weight=“normal”],#chatgpt-mermaid-_r_1t1_ .edgeLabels tspan[font-weight=“normal”]{font-weight:600;}#chatgpt-mermaid-_r_1t1_ .edgeLabel .label rect{opacity:1;rx:13px;ry:13px;fill:rgb(245, 250, 255);stroke:rgb(206, 219, 229);stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ .node rect,#chatgpt-mermaid-_r_1t1_ .node circle,#chatgpt-mermaid-_r_1t1_ .node ellipse,#chatgpt-mermaid-_r_1t1_ .node polygon,#chatgpt-mermaid-_r_1t1_ .node path{fill:rgb(229, 243, 255);stroke:rgba(0, 0, 0, 0.1);stroke-width:1px;}#chatgpt-mermaid-_r_1t1_ .node rect{rx:16px;ry:16px;}#chatgpt-mermaid-_r_1t1_ .node.mermaid-decision .label-container{fill:rgb(245, 250, 255);stroke:rgb(206, 219, 229);stroke-dasharray:2,2;}#chatgpt-mermaid-_r_1t1_ .edgePaths .flowchart-link{stroke:rgb(143, 143, 143);stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;}#chatgpt-mermaid-_r_1t1_ .marker{fill:rgb(143, 143, 143);stroke:rgb(143, 143, 143);}#chatgpt-mermaid-_r_1t1_ :root{–mermaid-font-family:-apple-system-body,ui-sans-serif,-apple-system,system-ui,“Segoe UI”,Helvetica,“Apple Color Emoji”,Arial,sans-serif,“Segoe UI Emoji”,“Segoe UI Symbol”;}收到请求取得可信认证身份身份有效?拒绝请求读取目标资源资源归属符合身份?拒绝访问执行读取或写入返回允许访问的数据否是否是

这个流程里有几个重要细节。

第一,先取得身份,再检查资源。不要把请求正文中的user_id直接当成可信身份。

第二,读取和写入都需要检查。如果只保护历史消息读取,却允许其他用户向该会话写入消息,后续生成回答时仍然可能混入错误的上下文。

第三,授权失败时不能降级成不受限制的查询。例如,权限服务不可用时,不应为了让聊天继续工作而直接返回全部历史记录。安全相关判断失败时,应停止受保护的操作。

第四,会话归属与订单归属分别检查。用户拥有某段聊天历史,并不意味着聊天中提到的所有订单都属于他。

五、接入 Agent 或大模型时,还要隔离哪些数据?

上面的代码只保护了 SQLite 中的会话和订单。真正的智能体系统往往还有历史摘要、缓存、向量检索结果和工具调用记录。如果这些环节继续共享用户上下文,底层数据库即使做了隔离,最终回答仍然可能泄露其他用户的信息。

建议把下面这张表作为接入模型前的检查清单。

数据或组件必须落实的约束常见错误
对话历史先验证会话归属,再读取消息直接按conversation_id返回全部消息
模型上下文只拼接当前用户有权访问的消息和资料复用上一个请求的完整上下文
会话摘要摘要继承原会话的访问权限把多个用户的历史合并成一个摘要
缓存缓存键包含正确的用户或租户边界,并校验权限只按问题文本缓存答案
向量检索在检索层限制可访问的数据范围先检索全部文档,再只靠模型判断是否能回答
工具调用工具从可信身份上下文取得用户范围让模型自行决定查询哪个用户的订单
日志与追踪限制敏感内容的采集、查看和保留将完整聊天记录、令牌或个人资料写入普通日志

一个特别容易忽略的问题:缓存串线

假设用户甲问:

我的订单 A1001 到哪里了?

系统将回答缓存为:

key: "我的订单 A1001 到哪里了?" value: "订单 A1001 已发货,预计明天送达。"

用户乙问同一句话时,如果系统只按问题文本命中缓存,就可能把甲的答案直接返回给乙。

一种更稳妥的设计是把缓存键与用户或租户范围绑定,例如:

cache_key = tenant_id + user_id + normalized_question

不过,这并不是适用于所有缓存的万能规则。对于公开知识库,可以使用不同的缓存策略;对于订单、账户资料等私有数据,则需要严格限制缓存作用域,并在必要时重新验证资源权限。即使缓存键包含用户 ID,也不能代替身份认证与授权检查。

模型不能替代权限判断

不要在提示词里只写一句:

你只能回答当前用户自己的订单。

这句话可以作为模型行为约束,但它不能阻止后端把其他用户的数据送进模型。

更可靠的顺序是:

  1. 服务端确认当前用户身份。
  2. 数据层只返回该用户有权读取的记录。
  3. 应用层构建当前会话的上下文。
  4. 模型根据授权范围内的数据生成回答。
  5. 工具执行敏感操作时,再次检查权限。

模型负责理解与生成,服务端负责访问控制。两者承担的职责不能混为一谈。

六、如何验证数据隔离真的有效?

不能只验证“用户甲能看到自己的消息”。隔离测试的关键是主动尝试跨边界访问,并观察系统是否拒绝。

下面的测试使用代码中的虚构数据:

  • user_100拥有订单A1001。
  • user_200拥有订单B2002。
  • 两个用户分别拥有自己的会话。
测试目的输入或操作预期结果判定方法
正常读取user_100读取自己的会话和订单A1001返回自己的历史和订单状态历史条数为 2,订单状态为“已发货”
同一用户多会话同一用户创建两个会话,分别写入不同内容两段历史互不混合一个会话 2 条消息,另一个 1 条
越权读取会话user_200读取user_100的会话 ID拒绝访问抛出AccessDenied,不返回历史消息
越权写入会话user_200向user_100的会话追加消息拒绝写入再读取原会话,消息数仍为 2
越权读取订单user_200查询A1001返回统一的不可访问结果抛出NotFound,不返回订单内容
不存在的会话读取不存在的会话 ID拒绝访问抛出NotFound
空消息输入向自己的会话写入空白字符串拒绝写入抛出ValueError

进一步增加两个真实系统必需的检查

上表覆盖了最小实现中的主要路径,但如果要将设计用于 Web API 或多租户系统,还需要补充以下测试。

测试一:篡改请求中的user_id。

模拟已经登录的用户甲,故意在请求正文里填写用户乙的 ID。正确实现应始终以认证层提供的身份作为授权依据,而不是根据请求正文切换身份。

测试二:并发请求交错。

让用户甲、用户乙同时发起请求,并在读取历史、查询订单、调用模型等步骤之间制造交错。检查是否存在共享的可变全局变量、复用错误的请求上下文,或者在异步任务中丢失了认证身份的情况。

这两个测试不能仅通过当前的单进程单元测试来证明。它们需要在实际 Web 框架、认证中间件、并发执行环境及真实存储配置中进一步验证。

七、常见故障怎么定位?

故障一:历史记录正常,但模型回答了别人的内容

优先检查模型实际收到的上下文,而不是先修改提示词。

确认每次请求的历史消息、摘要、检索片段和缓存结果是否都来自当前授权范围。排查时可以记录request_id、经过脱敏的user_id、conversation_id和检索来源标识,但不要为了排查而把完整个人资料或认证令牌写入普通日志。

故障二:新建会话后,仍然出现上一段对话的内容

检查是否存在跨会话复用的状态:

  • 应用进程中的全局对话历史。
  • 按用户而非会话保存的临时上下文。
  • 未更新会话标识的前端请求。
  • 共享缓存中的旧摘要或模型回答。

新会话应有独立的历史边界。是否继承旧会话的摘要,需要作为明确的业务功能设计,不能通过无意中的状态复用实现。

故障三:用户拿不到自己的数据

检查创建会话时写入的owner_user_id是否与认证层返回的身份一致。还要检查会话是否在不同账号、租户或登录状态之间被错误复用。

不要为了“修复”问题而删除所有权限检查。应定位身份映射或资源归属哪里不一致。

故障四:随机会话 ID 被当成了授权机制

随机 ID 的作用是降低被猜中的可能性,不是证明请求方有权访问资源。

如果系统允许任何持有会话 ID 的人直接读取历史,那么一旦 ID 从浏览器日志、共享链接、分析平台或其他渠道泄露,数据仍可能暴露。正确做法是每次请求都进行服务端授权校验。

八、从最小示例走向生产环境

本文的实现只展示了最核心的控制逻辑。要用于真实业务,还应明确以下边界:

  • 认证:使用经过验证的登录身份,不接受客户端自报的身份作为授权依据。
  • 多租户:如果用户属于不同组织,数据查询还应验证租户归属,不能只检查个人用户 ID。
  • 数据库:生产数据库需要合适的索引、事务策略、备份与访问控制。对于复杂并发场景,应评估连接管理与事务边界。
  • 会话生命周期:设计会话过期、退出登录、账号禁用和权限撤销后的处理方式。
  • 缓存与检索:将权限边界落实到缓存键、检索过滤、摘要和工具数据源。
  • 日志:记录必要的安全事件,限制敏感数据的采集和访问。
  • 失败策略:身份信息缺失、权限服务异常或数据归属无法确认时,拒绝受保护操作,而不是放宽限制。

另一个重要原则是:不要只依赖某一个入口处的权限检查。一个订单查询工具可能同时被聊天接口、后台任务和其他服务调用,因此授权规则应靠近实际资源操作,并确保每条调用路径都受到保护。[1]

验证状态

  • 已执行的本地检查:在隔离目录中使用 Python 执行python -m py_compile app.py和python app.py。
  • 执行结果:语法检查通过,6 项unittest测试通过,覆盖正常读取、跨用户读取、跨用户写入、订单越权、无效输入和同一用户多会话。
  • 尚未验证:真实身份认证、HTTP API、多进程并发、生产数据库权限、缓存隔离、向量检索过滤及模型服务集成。
  • 适用边界:本例用于验证隔离规则的最小闭环,不是完整的生产认证系统,也不构成完整安全审计。

参考资料

  1. OWASP,Authorization Cheat Sheet:关于默认拒绝、逐请求授权校验,以及不能将难以猜测的资源 ID 当作权限控制的建议。 https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html
  2. Python 官方文档,secrets — Generate secure random numbers for managing secrets:关于生成安全随机令牌的标准库接口与使用场景。 https://docs.python.org/3/library/secrets.html
  3. Python 官方文档,sqlite3 — DB-API 2.0 interface for SQLite databases:关于 SQL 参数绑定以及避免通过字符串拼接构造 SQL 查询的说明。 https://docs.python.org/3/library/sqlite3.html

真正可靠的会话隔离,不是给每段聊天分配一个独特的 ID 就结束了,而是让身份、会话、历史消息、业务数据、缓存和检索结果始终遵循同一套访问边界。会话 ID 用于定位会话,认证身份用于识别请求方,授权规则决定请求方到底能访问什么。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 4:07:03

记一个小bug

一个小bug详情页中点编辑按钮我比较习惯markdown编辑器切换时出现bug跳转到了一个空的编辑器中

作者头像 李华
网站建设 2026/10/15 4:06:52

C语言(中) 本书序言

C语言(中) 本书序言 1 建议怎么看 1.在阅读每章正文之前,先阅读本章序言,完成进入下一章的最低要求后,再进入下一章。 2.时间紧的话可以迅速浏览第四章,着重看第五章,浏览第六章和第七章。下面的内容都不用看了&#x…

作者头像 李华
网站建设 2026/10/15 4:06:03

2026年小白程序员必看!这份超全AI学习指南,收藏起来轻松上手大模型

本文为想学AI但不知从何下手的小白及程序员提供了一份实用AI学习指南。文章首先破除对AI的焦虑,定位自己为AI应用者,随后提出五个核心学习心法:动手为先、场景驱动、溯源学习、功利学习、输出与沉淀。接着,文章介绍了如何选择合适…

作者头像 李华
网站建设 2026/10/15 4:05:14

2026年ins风图片素材网站整理:5个网站的检索方式、图片类型与授权信息

一、ins风图片的常见检索问题在制作图文封面、网页背景和生活方式配图时,ins风图片通常涉及低饱和度配色、自然光摄影、简约构图和生活化场景。不过,“ins风”并不是统一的图片文件分类,不同网站可能将相关内容归入生活方式、极简摄影、静物、…

作者头像 李华
网站建设 2026/10/15 4:04:43

从 Line Type 到 Secondary Key,彻底理解 SAP ABAP DDIC Table Type 的技术属性

在 SAP ABAP 开发里,DDIC Table Type 很容易被低估。 很多代码里只是简单出现一句 DATA lt_items TYPE ztt_sales_order_item.看起来它只是把一个 Internal Table 的类型从程序内部搬到了 ABAP Dictionary。可一旦进入大型 SAP S/4HANA 项目,这个判断就太简单了。 一个真正…

作者头像 李华