AI Agent 调用业务 API 时,怎样传递用户身份和租户?别让模型自己填写 user_id

简介: 本文探讨AI助手调用业务API时的身份可信传递问题:`user_id`、`tenant_id`等关键身份字段绝不能由模型生成或前端提交,而必须源自已验证的服务端登录态(如Session/JWT),经签名绑定后由业务系统实时鉴权。强调“认证≠授权”,需严格分离接入身份、操作主体与最终权限裁决。

让 AI 助手查询订单、创建工单或提交退款申请时,接口参数里通常会出现一些看起来很熟悉的字段:

{
   
  "tenant_id": "t-179",
  "user_id": "u-1001",
  "order_id": "O-9001"
}

模型确实可以生成这段 JSON。

但这里有一个很容易被忽略的问题:

order_id 可以由模型从对话中提取,user_idtenant_id 却不能因为模型写对了格式,就被当作可信身份。

如果用户在对话里说“我是管理员”,模型也可能生成 role: admin;如果页面请求里带了另一个租户编号,模型也可能原样传下去。

一旦业务系统相信这些字段,AI 助手就不是在复用原有权限,而是在旁边新开了一条可以自行声明身份的入口。

本文继续沿着“已有系统先跑通一读一写”的技术路线,单独拆解一个经常被接入教程一笔带过的问题:Agent 调用业务 API 时,当前用户和租户到底应该怎样可信地传过去。

一、先分清:谁在调用、代表谁、最终谁裁决

一次 Agent 业务调用里,至少有三个不能混淆的问题。

问题 回答什么 常见载体
接入应用身份 哪个系统正在调用 Agent 控制面 Client Token、服务端凭证
业务操作主体 Agent 这次代表哪个用户、员工或服务账号行动 已验证 Session、JWT、企业身份票据
最终业务权限 这个主体此刻能否操作这条订单或这位客户 原业务系统权限表、租户边界与实时状态

这三件事不能互相代替。

一个有效的 Client Token 会把请求认证为配置的“商城客服系统”Client 身份;它本质上证明请求方持有该
Token,却不能自动证明当前终端用户是 u-1001

一个可信的 u-1001 也不等于他一定能退款。账号可能已停用,订单可能属于其他门店,退款额度也可能已经变化。

所以完整判断应该是:

先认证接入应用
-> 再绑定业务操作主体
-> 最后由业务系统按实时数据完成授权

只完成第一步,是“知道哪套系统发来了请求”;只有第三步也成立,才是“这次业务动作真的可以执行”。

二、为什么 user_id 不能来自提示词和模型参数

模型适合生成业务参数,例如:

订单号
查询条件
工单摘要
退款原因
用户希望修改的字段

但下面这些信息不应该由模型决定:

当前登录用户
当前租户
管理员角色
审批结果
业务权限集合
可信请求来源

原因很直接:模型处理的是不可信输入。

对话内容、页面标题、URL 参数、RAG 文档和工具返回都可能影响模型。它们可以帮助 AI 理解“用户正在看哪张订单”,却不能证明“用户是谁”。

下面几种接入方式都不可靠:

1. 在系统提示词里写当前用户

当前用户是 u-1001,租户是 t-179,请不要操作其他租户数据。

这可以帮助模型组织回答,但提示词不是鉴权系统。它不能防止上下文污染,也无法强制业务接口拒绝越权调用。

2. 让前端直接提交 user_id

{
   
  "message": "查询订单 O-9001",
  "user_id": "u-1001"
}

浏览器中的字段可以被修改。除非业务后端先根据登录态验证并重新建立身份,否则它仍然只是用户输入。

3. 给模型一个共享管理员 Token

这样做最省事,却相当于把所有 Agent 调用都变成同一个超级管理员。日志无法准确回答代表谁操作,原有租户和用户权限也失去了意义。

4. 把页面 visitor_id 当作登录身份

随机访客编号适合维持会话连续性,不适合授权。清理浏览器缓存、复制请求或修改 localStorage 都可能改变它。

一个简单原则是:

能被最终用户、浏览器或模型直接填写的身份字段,都只能当线索,不能直接当权限事实。

三、可信主体应该从哪里产生

可信主体应该来自业务系统已经拥有的身份链,而不是由 Agent 重新发明一套账号系统。

常见来源包括:

  • 已登录后台的服务端 Session;
  • 由业务后端验证过的 JWT;
  • 企业微信、钉钉或飞书验签后的成员身份;
  • 企业身份平台签发的短期票据;
  • 有明确权限范围的服务账号。

关键不在于所有企业使用同一种身份格式,而在于:身份必须由可信代码产生,全程不经过模型决定。

以商城后台为例,合理链路是:

用户登录商城后台
-> 商城后端通过 Session 确认 tenant=t-179、user=u-1001
-> 商城后端调用 Agent 控制面并写入可信主体
-> 模型只能选择允许的业务工具和填写业务参数
-> 控制面把主体连同任务一起签名发送给商城工具接口
-> 商城再次查询用户、租户、角色和订单状态
-> 有权则执行,无权则 403

这里没有要求商城把用户密码或完整权限表交给 Agent。中间只需要传递一个最小、稳定、可回到原权限体系解析的主体标识。

多租户系统可以使用不透明的结构化主体,例如:

t-179:u-1001
tenant_179:user_1001

具体格式由业务系统决定。重要的是,租户和用户两个维度应一起被可信地绑定,不能让模型单独覆盖其中一个。

四、在 BailingHub 中,这条身份链怎样落地

BailingHub(百灵中枢) 是一个开源、自托管的 Agent-to-Business(A2B)控制面。它把“哪个系统发起任务”“任务代表谁行动”和“业务系统最终是否允许”分成不同边界。

对于服务端 Client API 接入,业务后端先用自己的 Client Token 调用 /run

POST /run
Authorization: Bearer <只保存在业务服务端的 client token>
Content-Type: application/json

请求可以同时带上路由主体和工具操作主体:

{
   
  "request_id": "crm_order_9001_assist_1",
  "route": "order-support",
  "input": "查询订单 O-9001 是否符合退款申请条件",
  "metadata": {
   
    "principal": {
   
      "id": "u-1001",
      "tenant": "t-179",
      "roles": ["customer_service"],
      "audience": "employee"
    },
    "operator_subject": "t-179:u-1001"
  }
}

这两个字段在当前 BailingHub v0.3.3 中用途不同:

  • metadata.principal 用于归一化主体、路由 Audience 筛选和任务追溯;
  • metadata.operator_subjectmetadata 下的一级自定义字段,由路由中该工具源的 subject_field
    显式选中,作为实际工具调用主体。

对应的路由工具配置可以写成:

{
   
  "sources": [
    {
   
      "provider": "crm-tools",
      "allow": ["order.read", "refund.request"],
      "subject_field": "operator_subject"
    }
  ]
}

这里需要特别说明:当前版本不会自动把 principal.id 编码成工具调用的 X-Bailing-On-Behalf-Ofsubject_field 也不是点路径表达式。接入时要像上面这样显式提供顶层字段,不能只传 principal 后假定业务工具已经获得主体。

两份表示必须由同一个已经验证的服务端身份对象一次性派生,不能分别接受浏览器字段。BailingHub
v0.3.3 不会自动比对 principalsubject_field 最终是否指向同一主体;如果一边是 t-179
另一边却写成 t-180:u-2001,路由 Audience 身份和真实工具主体就会发生漂移。

这个细节看似多了一步,却能让接入方准确看见“路由身份”和“业务工具主体”各自来自哪里,避免依赖未经实现的隐式转换。

五、subject.required 解决的是“有没有主体”,不是“有没有权限”

业务接口可以在 OpenAPI 中声明这项能力必须绑定可信主体:

paths:
  /api/refund-requests:
    post:
      operationId: refund_request_create
      summary: 创建退款申请
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [order_id, reason]
              properties:
                order_id:
                  type: string
                  description: 要申请退款的订单号
                reason:
                  type: string
                  description: 退款原因
      x-agent-capability:
        version: 1
        enabled: true
        scope: refund.request
        risk:
          level: medium
        subject:
          required: true

Agent Capability Contract(ACC,Agent 能力契约)中的 subject.required: true 表达的是:这项能力在没有操作主体时没有意义。

BailingHub 会在工具装配阶段隐藏缺少主体的能力,调用层再做一次兜底。

但这仍不代表:

  • 主体一定真实;
  • 主体属于正确租户;
  • 主体当前仍然有效;
  • 主体对具体订单有权限;
  • 主体满足当前退款规则。

这些都要由业务系统执行。

subject.required 是能力治理声明,不是企业权限数据库的副本。

六、为什么主体和任务号必须一起进入签名

BailingHub 调用业务工具时,会发送类似下面的请求头:

X-Bailing-On-Behalf-Of: t-179:u-1001
X-Bailing-Job-Id: job_...
X-Bailing-Timestamp: ...
X-Bailing-Signature: sha256=...

签名材料覆盖:

时间戳
HTTP 方法
路径和查询串
请求体哈希
On-Behalf-Of 主体
Job-Id 任务号

精确构造是:

sha256=HMAC_SHA256(
  tool_secret,
  "<ts>.<METHOD>.<path?query>.<sha256hex(raw_body)>.<On-Behalf-Of>.<Job-Id>"
)

其中 <ts> / X-Bailing-Timestamp 是 Unix 秒,官方参考验签窗口默认为 300 秒;METHOD 使用大写,
HMAC-SHA256 输出使用小写 hex。path?query 使用中枢实际发出的路径和查询串;请求体按原始 UTF-8 字节
计算 SHA-256 hex,GET 请求体为空串。验签端不要先 decode 查询串,也不要把 JSON 反序列化后重新拼装,
否则同一份业务数据也可能因为字节不同而得到另一份签名。

如果主体不在签名里,攻击者拿到窗口内的合法请求后,可能只替换 X-Bailing-On-Behalf-Of,把同一组业务参数换成另一个用户或租户执行。

把主体和任务号一起签进去,可以让这种修改导致验签失败,也能把业务结果和 Agent 任务关联起来。

但验签通过只回答一个问题:

这份请求由持有工具源 Secret 的调用方按这些内容签发,途中没有被篡改;正常部署中,这个调用方是
配置了该 Secret 的 BailingHub 工具代理。

它没有回答:

t-179:u-1001 现在是否有权操作订单 O-9001

后一个问题仍然属于业务系统。

七、业务接口必须做“验签 + 授权”两道闸

业务侧处理函数可以遵循下面的顺序:

1. 校验时间窗和 HMAC 签名
2. 读取签名覆盖的 X-Bailing-On-Behalf-Of
3. 解析 tenant 与 user
4. 查询当前账号状态和租户归属
5. 按当前接口、资源和业务状态执行原权限判断
6. 允许则继续,任何缺失、歧义或异常都拒绝

伪代码示例:

const verified = verifyBailingToolCall(request, TOOL_SECRET)
if (!verified.ok) return response.status(401).end()

const principal = parseSubject(verified.onBehalfOf)
if (!principal) return response.status(403).end()

const operator = await users.findActive(principal.tenant, principal.user)
if (!operator) return response.status(403).end()

const order = await orders.findInTenant(principal.tenant, request.body.order_id)
if (!order) return response.status(404).end()

if (!permissions.canRequestRefund(operator, order)) {
   
  return response.status(403).end()
}

return createRefundRequest(operator, order, request.body)

业务接口还应拒绝或忽略工具参数中的 user_idtenant_idrole 等身份声明。授权只能使用已经验签的
X-Bailing-On-Behalf-Of;否则即使主体头本身安全,错误实现仍可能在后续代码里重新相信模型参数。

不要把授权回调写成:

authorize: () => true

这只完成了“认证中枢”,没有完成“授权用户”。只要请求签名正确,任何主体都可能获得同样的业务能力。

Agent 调用和人在后台点击按钮,最终应该回到同一套权限与业务状态判断,而不是为 AI 开一条默认放行的快捷通道。

八、网页聊天入口怎样绑定登录用户

如果 AI 助手嵌在商城或 CRM 页面中,浏览器仍然不应该拿到 Client Token 或工具源 Secret。

BailingHub 的网页聊天链路支持由业务后端签发短期访客票据:

浏览器先登录业务系统
-> 业务后端根据 Session 取得真实 tenant + user
-> 业务后端用接入方 Token 签一张短期票据
-> 页面只把短票据交给聊天组件
-> BailingHub 验签后写入可信 visitor_uid
-> 工具调用优先按 metadata[subject_field] 取得主体,取不到时回落到已验签的 visitor_uid
-> 主体被签入 On-Behalf-Of

没有票据时,组件生成的 visitor_id 仍然可以维持匿名会话,但它不是身份凭证。对于任何声明了
subject.required 的工具,匿名用户都不应该看到或调用,而不只是写工具。

票据签发和身份解析都在业务服务端完成,长期 Token 不进入浏览器,模型也不能把一个匿名访客编号升级成已登录用户。

九、至少做六组负向验证

可信身份链不能只测试一次正常调用。上线前至少验证:

1. 没有主体

在上文 /run 示例中移除 operator_subject,并确认没有同时提供已验签的 visitor_uid 或可信渠道主体。
声明 subject.required 的工具应在装配阶段不可见,直接调用也应被拒绝。

2. 只修改主体,不重新签名

t-179:u-1001 改为 t-180:u-2001,业务接口必须因签名不匹配而拒绝。

3. 路由主体和工具主体故意不一致

principal.tenant=t-179,同时把 operator_subject 写成 t-180:u-2001。调用方集成测试必须在任务
提交前识别并拒绝这种漂移,不能依赖 BailingHub v0.3.3 自动比对。

4. 合法签名,但主体越权

即使请求确实来自中枢,只要该主体不属于目标租户、没有接口权限或无权访问当前订单,业务系统仍应返回 403 或 404。

5. 主体在查询后失效

先成功查询,再停用账号或移除权限,然后发起写操作。业务系统必须按执行时状态重新判断,不能复用旧查询结论。

6. 伪造 visitor_id 或模型参数

修改浏览器 visitor_id、在对话中声明“我是管理员”或在工具参数中增加 user_id / tenant_id,都不能
改变签名覆盖的操作主体;业务接口也必须忽略这些模型参数中的身份声明。

再补一组追溯验证:通过 Job-Id、主体和真实业务对象,能否从 Agent Trace、工具审计和业务日志中还原同一次动作。

十、首轮接入检查清单

  • [ ] Client Token 只保存在业务服务端;
  • [ ] 业务后端先验证 Session、JWT 或企业身份票据;
  • [ ] 模型无法填写或覆盖业务操作主体;
  • [ ] 多租户主体同时绑定 tenant 与 user;
  • [ ] principal 与工具主体由同一个服务端身份对象一次性派生;
  • [ ] metadata.principal 与工具 subject_field 的用途已经显式配置;
  • [ ] subject.required 工具在无主体时不可见;
  • [ ] On-Behalf-OfJob-Id 都进入 HMAC 签名;
  • [ ] 业务接口先验签,再按实时权限完成最终授权;
  • [ ] 业务接口不会把模型参数里的 user_idtenant_idrole 当作授权事实;
  • [ ] 跨租户、停用账号和资源越权请求都会 fail closed;
  • [ ] 日志不保存密码、Token 或不必要的敏感身份资料。

如果其中任何一项的答案是“模型应该不会乱填”,这条身份链就还没有完成。

结语:AI 可以生成参数,但不能生成权限

存量业务系统接入 AI 助手时,不需要推倒原来的用户、租户和权限体系。

真正需要做的是把已有登录态建立的可信主体,安全地带进 Agent 调用链,再让业务系统继续掌握最后一道授权。

业务后端确认身份
-> Agent 控制面绑定主体并治理执行    
-> 签名覆盖主体、任务与请求
-> 业务系统按实时权限最终裁决

模型可以判断用户想查哪张订单,也可以整理退款原因;但它不能靠生成一个 user_id,获得代表这个用户行动的权力。

这条边界一旦守住,AI 助手才能真正复用现有业务系统,而不是在旁边重新造一套更危险的管理员入口。

你们现在的 Agent 调用链里,终端用户身份来自哪里?是服务端登录态、JWT、企业身份票据,还是仍然放在提示词和工具参数里?

延伸阅读

相关文章
|
5月前
|
人工智能 运维 监控
让问题不过夜:交易领域“问诊”Agent实践
在日常研发支持中,工程师频繁穿梭于工单、群聊、舆情反馈与问题排查之间:一边解释业务规则与口径,一边追踪链路、查看日志、核对指标、执行补偿。这些工作高度碎片化、重复性强且严重依赖个人经验,导致响应效率低、处理质量不稳定、新人上手困难。 为此,我们围绕“研发支持中的问诊痛点”,构建了一个可持续运营的智能 Agent 系统。通过将一线高频问题抽象为两类核心能力形态(业务答疑与问题诊断),并结合“排查文档技能化 + 质量评分闭环”机制,实现解释与排查工作的前置自动化。该系统不仅“能跑”,更能持续迭代进化,显著缩短首响时间与平均解决时长,提升服务一致性与工程效能。
让问题不过夜:交易领域“问诊”Agent实践
|
2月前
|
缓存 弹性计算 运维
运维不再需要“老师傅”——OS 运维 Skills 发布,欢迎体验
让任何运维 Agent 具备资深内核专家的诊断能力。
|
3月前
|
缓存 人工智能 运维
SysOM Agent智能运维系列:Pod内存高告警,一次对话30秒定位根因
让内存诊断从"靠经验排查"变成"可解释、可复现、可执行"的工程化流程。
|
人工智能 运维 自然语言处理
智能运维新范式:阿里云网络 AI Ops Skills 赋能企业数字化转型
阿里云推出AI Ops Skills系列工具,以“自然语言即接口”理念革新网络运维:5大智能Skill覆盖故障诊断、EIP管理、全球加速、HTTPS升级和IPsec VPN,支持对话式操作、全流程自动化、安全审计与开箱即用,大幅提升效率、降低门槛、保障合规。(239字)
908 0
智能运维新范式:阿里云网络 AI Ops Skills 赋能企业数字化转型
|
7月前
|
人工智能 自然语言处理 API
数据合成篇|多轮ToolUse数据合成打造更可靠的AI导购助手
本文提出一种面向租赁导购场景的工具调用(Tool Use)训练数据合成方案,以支付宝芝麻租赁助理“小不懂”为例,通过“导演-演员”式多智能体框架生成拟真多轮对话。结合话题路径引导与动态角色交互,实现高质量、可扩展的合成数据生产,并构建“数据飞轮”推动模型持续优化。实验表明,该方法显著提升模型在复杂任务中的工具调用准确率与多轮理解能力。
1003 43
数据合成篇|多轮ToolUse数据合成打造更可靠的AI导购助手
人工智能 运维 安全
39 1
人工智能 缓存 API
46 0
JSON 缓存 API
56 3
|
13天前
|
安全 算法 BI
只读、幂等、超时和限流为什么属于能力声明?
本文提出Agent能力声明中必需的四项最小执行语义:`readonly`(真实业务只读)、`idempotent`(参数级幂等)、`timeout_ms`(客户端等待边界)和`rate_limit`(调用频率提示)。它们独立正交,不替代业务实现或运行时策略,而是提供跨平台一致理解的基础信号,防止因隐式假设导致重试错、超时误判、限流缺失等高危问题。(239字)
|
14天前
|
存储 人工智能 缓存
审计日志、应用日志和 Trace 到底有什么区别?
本文厘清Agent系统中三类关键日志的本质差异:应用日志(诊断系统异常)、Trace(追踪请求链路)、审计记录(还原业务责任)。强调审计不可被日志或Trace替代——它需明确记录谁、以何主体、凭何依据、用何参数、经何审批、达何结果,支撑真实可溯的权责认定。(239字)

热门文章

最新文章