AI Agent 的 OAuth 实践:安全地代表用户执行操作

AI Agent 如何安全地代表用户执行操作?本文深入解析适合 Agent 的 OAuth 流程、安全存储及刷新机制,教你构建低风险的委派授权体系,防范凭证泄露。

用 Apifox,节省研发团队的每一分钟

AI Agent 的 OAuth 实践:安全地代表用户执行操作

免费使用 Apifox

相关推荐

最新文章

API

一体化协作平台

API 设计

API 文档

API 调试

自动化测试

API Mock

API Hub

立即体验 Apifox
目录

您的 Agent 需要读取客户的日历、以其账号发送消息,或以其名义提交工单。最快捷的做法是拥有一个具有广泛访问权限的服务账号,并通过它来执行操作。这样一来,每个操作都会显示为“该集成”所执行,没人能分清是哪个用户触发了什么,而且一旦凭证泄露,您所涉及的所有账号都将面临风险。

正确的做法是委派授权(delegated authorization):用户向您的 Agent 授予一个有范围限制且可撤销的 Token,Agent 代表该用户执行操作,审计日志中也会记录该用户的名字。这正是 OAuth 2.0 的设计初衷。但对 Agent 来说,尴尬之处在于 OAuth 假设存在浏览器以及一个亲自点击“允许”的用户,而 Agent 通常在凌晨 3 点在后台运行。

本指南将介绍哪种 OAuth 流程适合 Agent、如何限制 Token 的范围和进行存储、如何处理更新和撤销,以及如何在没有真实账号的情况下测试整个路径。如果您仍在基于密钥的认证和委派授权之间纠结,可以先阅读我们关于 API 密钥与 OAuth 的对比文章。

Apifox 可以帮助解决团队容易低估的环节:在 Agent 部署到生产环境之前,测试该流程的每一个分支,包括 Token 过期和撤销。

AI Coding 交流群

如果你也在用 AI 写代码,或者正在研究 Cursor、Claude Code 这些工具,欢迎加入以下交流群。群里平时会聊一些 AI 编程的实际用法、开发工作流,还有各种新工具和新玩法。

服务账号还是委派访问

请谨慎选择,因为这两种模型的失效机制不同。

服务账号是 Agent 自身的身份,拥有其独立的权限。它适用于 Agent 代表您执行的工作:读取您自己的数据库、调用您自己的内部服务、针对您的基础设施运行定时任务。请严格限制其范围(如我们在关于 Agent 最小特权 API 密钥的文章中所述),并定期进行轮转。

委派访问则是 Agent 代表特定用户执行操作,仅拥有该用户的权限,绝不越权。每当数据属于他人时,就需要使用这种方式。以下三个特性让这些额外的工作物有所值:用户可以查看已授予的权限、用户可以撤销它,且每个操作的日志都包含该用户的身份。

要避免的失效模式是:使用具有组织级访问权限的服务账号来“扮演”用户。这种方式确实行得通,但也意味着一旦单个凭证泄露,所有人都会暴露,且无法针对单个用户进行撤销,也无法提供真实的审计轨迹。

哪种流程适合 Agent

OAuth 2.0 定义了多种授权类型,但这里只有少数几种适用。OAuth 2.0 规范中包含了完整的内容;而以下是您将会用到的类型。

带 PKCE 的授权码模式。 代表用户执行操作的标准流程。用户被重定向到提供商,批准作用域(scopes),然后您的服务使用该代码兑换 Token。根据 OAuth 2.0 安全最佳当前实践,PKCE 可以保护该交换过程,目前是所有客户端类型的默认推荐配置。我们关于授权码授权的详细教程将分步介绍其工作机制。

针对 Agent 的关键点在于:这个流程在连接时运行一次,且需要人类用户在场。Agent 本身永远不会运行它,而是使用该流程生成的 refresh token。在你的设计中将这两个时刻分离,大部分令人尴尬的复杂问题就会迎刃而解。

Client credentials(客户端凭证)。 机器对机器(M2M),不涉及用户。适用于服务账号,但不适用于代表用户进行操作,因为没有用户来进行授权同意。

Device authorization grant(设备授权许可)。 适用于没有浏览器的机器上的 Agent。用户获取一个代码并在手机上进行批准。对 CLI Agent 和无头(headless)环境非常有用。

Token 交换。 RFC 8693 允许服务将一个 Token 交换为权限更窄的 Token。这就是你如何将一个派生自用户更广泛授权的、仅限于某个特定任务单一 Scope 的 Token 授予子 Agent,而无需交出原始 Token 的方法。如果你运行多 Agent 系统,这就是让每个 Agent 拥有独立凭证变得切实可行的机制,并且它符合我们关于多 Agent 协同(multi-agent handoff)文章中的边界规则。

严格限制 Scope,并为每个 Agent 单独配置

Scope 是委托访问(delegated access)发挥价值的地方,也是大多数实现方式容易偷懒的地方——它们往往会请求应用可能需要的所有权限。

仅请求该 Agent 所需的权限。例如,一个日程安排 Agent 只需要日历的写入权限,不需要其他任何权限——不需要邮件、不需要联系人,也不需要文件。用户会查看授权同意页面,而冗长的权限列表既会导致信任问题,也会扩大爆炸半径(blast radius)。我们关于 OAuth 2 scope 的解析文章介绍了服务提供商是如何对它们进行建模的。

采用渐进式请求。在连接时仅请求最少权限,然后当用户请求需要新权限的功能时,再请求更多权限。与具体请求绑定的授权更容易获得用户的批准,也更容易说得通。

为每个 Agent 提供独立的 Token。如果一个调研 Agent 和一个计费 Agent 都代表同一个用户操作,请派生出两个具有不同 Scope 的 Token,而不是共享同一个。这样,一旦调研 Agent 被攻破,攻击者也无法进行退款,而且日志能清楚地告诉你具体是哪个 Agent 进行了操作。

默认优先使用只读 Scope,并在需要写入时进行显式的权限提升。结合我们在关于 AI Agent 防护栏(guardrails)文章中提到的对破坏性调用的审批关卡,这样拥有写入权限的 Token 就不会成为阻止 Agent 犯错的唯一防线。

存储、刷新与撤销

Token 即凭证,因此请像对待凭证一样对待它们。

存储。 对静态存储的 refresh token 进行加密,并针对每个用户使用不同的密钥。切勿将它们写入日志,切勿将它们放入 prompt 中,也切勿让模型看到它们。上下文中的 Token 会出现在你的追踪(trace)存储、服务提供商的日志以及可能的摘要中。我们关于追踪 Agent 工具调用的文章介绍了如何在边界处进行脱敏,而不是在读取时脱敏。

刷新。 访问令牌(Access token)在设计上就是短效的。Agent 本身永远不应该管理刷新过程;HTTP 客户端前面的 Token 管理器应该在令牌临近过期时进行刷新,并在遇到 401 错误时自动重试一次请求。

class TokenManager:
    def __init__(self, store, provider):
        self.store, self.provider = store, provider

def access_token(self, user_id, agent_scope):
        rec = self.store.get(user_id, agent_scope)
        if rec.expires_in() > 60:
            return rec.access_token
        fresh = self.provider.refresh(rec.refresh_token, scope=agent_scope)
        self.store.save(user_id, agent_scope, fresh)   # rotation: store the new refresh token
        return fresh.access_token

有两个细节至关重要。首先,服务商正越来越多地轮换 refresh token,即在每次刷新时都会颁发一个新的并使旧的失效,因此请立即持久化保存新的 token,否则会将用户拒之门外。另外,应当按用户对刷新操作进行串行化,因为对于有轮换机制的服务商,两个并发的刷新操作会产生竞态,导致其中一个失败。

撤销。 用户撤销访问权限、Token 过期、管理员删除账号。Agent 必须将 401403 视为终态错误,而非可重试错误。重试 auth 失败毫无用处,还可能触发滥用保护机制。请遵循我们关于 Agent API 错误设计的文章中的错误模式,返回一条明确指明用户和 scope 的清晰信息,以便人工进行处理。

授权同意问题

Agent 结合 OAuth 的尴尬之处在于:授权同意需要人工参与,而 Agent 是无人值守运行的。

将连接阶段与运行阶段分离,问题就会变得易于管理。在连接时,用户通过浏览器授权一次,你保存一个 refresh token。在运行时,Agent 可以在无需人工干预的情况下使用该授权。这适用于定时运行和后台运行的 Agent,而大多数 Agent 都是如此运行的。

需要防范两个限制。首先,授权会过期,有时是因为数月未使用,有时是因为策略限制。应检测已过期的授权,停止运行并通知用户,而不是每晚在后台默默失败。其次,授权同意存在 scope 上限:如果 Agent 需要一个用户从未授予过的 scope,它必须主动请求,而不是自行提升权限。

对于任何高风险操作,请在执行时增加第二道防线。Token 证明了 Agent 可以 执行该操作;而审批关卡则决定了它 是否应该 执行。这是两个完全不同的问题,都需要明确的答案。

在 Agent 接入前测试该流程

Auth 代码路径通常是大多数集成中测试最少的部分,因为手动执行它们意味着需要点击服务商提供的各个页面。

构建以下五个用例:

  • 正常路径(Happy path)。 有效的 access token,成功的调用。这是基准。
  • 已过期的 access token。 服务商返回 401,管理器进行刷新,请求重试一次并成功。这是实际中最常见的路径,但往往测试最少。
  • 已撤销的 refresh token。 刷新返回 invalid_grant。Agent 必须停止运行并报告错误,而不是循环重试。
  • Scope 不足。 返回带有 scope 错误的 403。Agent 决不能重试,且必须说明缺失了哪个 scope。
  • 并发刷新。 同时为同一个用户发起两次调用。应当只发生一次刷新。

在 mock 上运行它们。在 Apifox 中,你可以定义 Token 接口和受保护的接口,然后 mock 每个响应(包括错误 body),这样整个矩阵就可以在不接触真实提供商的情况下运行。我们关于在 mock 而非生产环境上运行 agent 的文章涵盖了更广泛的习惯,而我们的 OAuth 2 API 测试指南则涵盖了请求级别的细节。

三种集成及其所需内容

日历助手。 读取可用性并为单个用户预订会议。委派访问、两个 scope、浏览器中的连接时同意,随后在后台运行。一个有趣的失败案例是撤销:用户断开了集成,夜间运行必须检测到这一情况并停止,而不是在一周内不断重试已失效的授权。

共享收件箱中的支持 agent。 处理属于团队的工单。在这里,身份问题变得更加尖锐。作为团队的共享账户进行操作是合理的,因为资源确实属于团队,但这样一来,审计日志中的每一条回复看起来都完全相同。更好的方案是使用具有自己 scope 的机器人身份,并记录是哪个人触发了运行,这既能保持归属关系的完整性,又不会假装该 agent 是一个人。

内部运维 agent。 在你自己的基础设施中重启服务并读取仪表板。没有用户数据,没有委派。具有窄范围 scope 的服务账户(service account)是正确的解决方案,工作重点在于轮换和爆炸半径(blast radius),而不是同意授权。

分界线在于所有权。如果数据属于可能合理地想要撤销你访问权限的人,请使用委派认证。如果数据属于你,请使用服务账户,并将精力集中在 scope 划分上。

在归属关系中保留人类角色

委派认证回答了“代表谁”的问题。它没有回答“应谁的要求”,而对于 agent 工作,你两者都需要。Token 证明了 agent 可以代表用户行事;但它并不记录是哪个人请求了该运行。

将第二种身份与工作紧密结合。在 agent 执行分配任务的场景中,工作管理层是放置该身份的天然场所:例如 HiFox 任务不仅记录了负责该工作的人,还记录了被分配执行该任务的 Agent 或 Crew,从而将人类责任与 agent 执行作为两个独立且可见的事实呈现。 HiFox 文档详细描述了这种拆分。无论你如何存储它,发生事件后的审计问题通常是“谁请求了这项操作”,而单凭 Token 是无法回答的。

不要让模型持有凭据

有一条架构规则可以防止 agent 系统中大多数认证事件的发生:模型永远接触不到 Token。

在模型选择工具并生成参数后,执行器会在 HTTP 层注入 Token。工具的数据模型不包含 token parameter,Prompt 中不含有凭证,并且模型读取的响应已剥离了 Authorization header。

这对 Agent 而言比普通客户端更为重要,因为这涉及到模型输入的流向。上下文中的任何内容都可能被总结为交接信息、写入追踪日志(trace)、在错误信息中回显,或者返回给要求 Agent 解释其行为的用户。这些路径都不是恶意的,它们都是正常的功能,但只要凭证暴露在作用域内,它们就会瞬间变成泄露源。

同样的规则也适用于用户身份。执行器知道当前运行是代表哪个用户执行的,并据此选择 Token。让模型来指定用户,相当于把授权决策交给了系统中预测性最差的组件。

清单

  • 只要数据属于用户,就使用委派访问(Delegated access);仅在访问您自己的资源时使用服务账号(Service accounts)。
  • 连接时使用带有 PKCE 的授权代码(Authorization code),无头(headless)设备使用设备授权(device grant)。
  • 按 Agent 请求 Scope(作用域),保持最小权限,并逐步提升。
  • 子 Agent 获取的是兑换的 Token,而不是用户原始授权的副本。
  • 静态加密 Refresh Token,且绝不将其放入 Prompt、日志或追踪中。
  • 刷新操作由 Token 管理器处理,按用户进行序列化,并持久化轮转(rotation)状态。
  • 401403 视为终结状态,并返回包含用户名和 Scope 的提示信息。
  • 检测已过期的授权并向用户提示,而不是在后台例行任务(如夜间任务)中不断重试。
  • 在高危操作中,在 Token 的基础上增加审批关卡。
  • 在 CI 中使用 mock 对所有五种 auth 场景进行测试。

相比于共享密钥,委派 auth 需要做更多的工作,但当 Agent 代表他人执行操作时,它能为您提供两个不可或缺的安全保障:用户可以随时撤回授权,且日志能明确记录是谁在什么时间做了什么。下载 Apifox,在 Agent 无人值守运行之前,构建 Token 流及其失败用例。

常见问题解答

Agent 能否自行完成 OAuth 同意流程? 不能,而且它也不应该尝试这样做。同意流程需要由人来决定授予什么权限。应让用户通过正常的浏览器流程授权一次,然后让 Agent 使用生成的授权凭证。

每个 Agent 是否应该拥有自己的 OAuth 客户端? 每个产品集成应使用独立的客户端,而其中的每个 Agent 应使用独立的 Token(通常通过 Token 兑换获取)。当服务提供商对每个客户端应用速率限制,或者您希望进行独立撤销时,使用不同的客户端会很有帮助。

如果 Refresh Token 进行了轮转,而我错过了新的 Token 会怎么样? 用户将被锁定并需要重新连接。在消耗旧 Token 的同一个事务中持久化新的 Refresh Token,并按用户对刷新操作进行序列化,以避免两个工作线程发生竞态条件。

让模型看到访问令牌安全吗? 不安全。Token 属于 HTTP 层,应由你的执行器注入。任何模型看到的内容都可能最终出现在追踪记录、摘要或响应中,正如我们在关于 Agent 最小权限 API key 的文章中所讨论的那样。

如何审计哪个 Agent 进行了什么操作? 在每次调用时记录用户 ID、Agent 名称、所使用的作用域以及 Token 标识符,切勿记录 Token 本身。我们关于追踪 Agent 工具调用的文章中介绍了具体的记录结构。

如果服务商不支持 Token 交换怎么办? 如果服务商允许,可以为每个 Agent 分别存储独立的授权;或者在自己的网关中强制收窄作用域,以便每个 Agent 的调用在离开你的网络之前,都被过滤限制在其允许的操作范围内。

开发必备:API 全流程管理神器 Apifox

介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。

如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用

Apifox

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案

获取专属报价与部署方案

icon 详细的私有化部署系统架构与安全白皮书
icon 针对您公司规模的专属报价单
icon 免费的 1v1 专属产品演示 (Demo) 机会
获取部署方案
* 提交后,我们的客户经理将在 1 个工作日内与您联系
林俊锋 企业微信
@Apifox 专属顾问
扫码备注: 私有化 + 公司名