UMA 全称 User-Managed Access(用户管理访问),是一种建立在 OAuth 2.0 之上的授权协议。它的核心思想是把“资源所有者”对资源的访问控制权完全交还到资源所有者手中,由资源所有者自己决定哪个客户端、哪个用户在什么条件下可以访问自己的受保护资源。与 OAuth 2.0 常见的“授权码模式”不同的是,UMA 更强调资源所有者的持续控制能力。
那这套协议和 Agent 有什么关系?随着 LLM 驱动的 Agent 在 2024、2025 年快速走进生产环境,Agent 不再是单纯的“聊天机器人”,它会代替用户去访问日历、邮件、网盘、健康数据、企业系统。这时候出现了一个关键问题:Agent 到底有没有权限访问这些数据?权限边界在哪里?用户如何控制 Agent 的行为?
本指南将围绕 UMA 的核心概念、授权流程、与 Agent 的集成模式、实际代码示例和调试建议展开。适合正在做 Agent 应用、想要给 Agent 增加安全和权限控制的开发者,也适合刚开始接触 UMA、想搞清楚它到底是什么的后端工程师。
1. UMA 与 Agent 的关系
1.1 先理解 Agent 的“权限困境”
在现在的 Agent 应用中,我们经常看到这样的架构:Agent 拿到用户的一句指令,然后调用一个工具函数去获取数据。问题在于,这个“工具函数”背后往往绑定了一套静态凭据,或者一个过宽的 API Key。
举例来说,一个日历 Agent 如果绑定了用户的 OAuth Token,那么用户无法精细控制“Agent 只能读取未来 7 天的日程,不能读取历史日程”“Agent 只能读取,不能修改”。更复杂的是,当 Agent 需要代表用户去另一家公司获取数据时,信任边界一下子变宽了——用户凭什么信任这个 Agent?Agent 又凭什么向对方证明自己有权访问?
这种场景下,传统 OAuth 2.0 的授权模型不够灵活。OAuth 2.0 关注的是“用户授权客户端访问资源”,但授权粒度通常在 Token 颁发时确定,后续很难动态调整。而 UMA 提供了一套更贴近真实业务需求的动态授权管理协议。
1.2 UMA 能解决什么问题
UMA 解决的核心问题是:资源所有者在授权之后,依然可以对资源进行持续、细粒度的访问控制。
我们用一个更具体的例子来理解:
用户 Alice(资源所有者) ↓ 拥有 日历服务中的敏感日程(受保护资源) ↓ 授权 Bob 公司的日程分析 Agent(请求方) ↓ 想要访问 某一天的日程数据在 UMA 流程中,Alice 并不是把整个日历全部开放给 Agent,而是:
- Agent 请求访问某一天的日程数据。
- 日历服务(资源服务器)发现请求方没有足够的权限,返回一个“需要授权”的提示,并带上了授权服务器的地址。
- Agent 去授权服务器请求权限。
- 授权服务器询问 Alice:“Bob 公司的 Agent 想要访问你 3 月 28 日的日程,是否允许?”
- Alice 可以选择允许、拒绝,或者设置更细的策略(例如只允许读取摘要,不允许读取地点)。
- 授权服务器颁发一个权限令牌(Permission Token),Agent 持令牌访问资源服务器。
- 资源服务器验证券,返回数据。
这套流程最大的特点在于:每一次访问都可以触发一次策略评估,资源所有者可以随时撤销或调整权限,而不需要重新颁发 Token。这对动态、长时运行、多步骤推理的 Agent 来说,是一个安全且透明的授权模型。
1.3 为什么开发者需要掌握 UMA
如果你在开发任何具备以下特征的 Agent,就需要认真考虑 UMA:
- Agent 需要代表用户访问第三方数据(日历、邮箱、健康记录、企业系统)。
- Agent 运行在云端,权限不能写死在前端或后端代码中。
- Agent 的权限需要被用户实时查看和回收。
- 多个 Agent 共享同一套数据资源,但需要不同精细度的访问能力。
- 合规要求强制要求数据访问可审计、可追溯。
即便你现在只是在做一个简单的 RAG(检索增强生成)应用,理解 UMA 也会帮助你构建更合理的权限边界,避免把“能读到所有文档”的内网权限暴露给一个只应该读取特定目录的 Agent。
2. UMA 核心概念与授权流程拆解
2.1 参与方角色
在 UMA 2.0 中,主要参与方有四个:
- 资源所有者(Resource Owner):拥有受保护资源的实体,通常是终端用户。
- 资源服务器(Resource Server,RS):托管资源的服务器,例如 WebDAV 服务器、日历服务、文件存储。
- 授权服务器(Authorization Server,AS):负责管理资源所有者的授权策略、颁发令牌。UMA 的授权服务器通常带有策略引擎。
- 请求方(Requesting Party,RqP):这里的“请求方”可以是人,也可以是 Agent。在经典 OAuth 中我们称之为 Client,在 UMA 中更准确地说,是希望访问受保护资源的客户端软件——也就是 Agent 本身。
有些文档里还会提到“客户端(Client)”和“请求方”的区别。简单理解:客户端是运行 Agent 的软件,请求方是使用该软件来获取数据的实体。当 Agent 代表用户发起请求时,请求方可以视为 Agent 自身。
2.2 UMA 授权流程分步解析
我们可以把 UMA 授权流程拆成三个阶段:发现、请求、许可。
第一阶段:发现
Agent 拿着一个资源标识符去访问资源服务器,资源服务器返回一个 401 响应,并携带WWW-Authenticate头,其中包含as_uri,也就是授权服务器的地址。
HTTP/1.1 401 Unauthorized WWW-Authenticate: UMA realm="example", as_uri="https://auth.example.com"Agent 解析这个头部后,就知道应该去哪个授权服务器申请权限。
第二阶段:请求
Agent 向授权服务器注册自己的客户端信息,获取client_id,然后请求一个权限令牌。这里有一个关键点:UMA 的权限令牌不是直接访问资源的 Access Token,而是一个“代表请求方意图”的令牌。
第三阶段:许可
授权服务器根据资源所有者设定的策略进行决策。如果还没有策略,授权服务器会向资源所有者发送通知,请求其做出同意或拒绝的决定。资源所有者可以预先设置自动化策略,也可以每次都手动审批。
一旦资源所有者同意,授权服务器会返回一个permission ticket,Agent 再拿着这个 ticket 去换取最终的 Access Token。
2.3 UMA 与 OAuth 2.0 的关键差异
很多初次接触 UMA 的开发者会产生困惑:UMA 看起来和 OAuth 2.0 Client Credentials 或 Authorization Code 流程差不多,到底有什么区别?
| 维度 | OAuth 2.0 | UMA |
|---|---|---|
| 授权主体 | 资源所有者 | 资源所有者 |
| 访问粒度 | Token 颁发时确定 | 每次访问可动态评估 |
| 资源所有者参与度 | 登录时授权一次 | 可在授权后持续调整策略 |
| 典型适用场景 | 用户授权 App 访问自己的数据 | Agent、IoT、跨域数据共享 |
| 策略管理 | 较弱,通常不内建策略引擎 | 授权服务器内建策略引擎 |
| 令牌生命周期 | Access Token / Refresh Token | Permission Ticket / RPT(Requesting Party Token) |
| 审计追踪 | 依赖实现 | 授权服务器可记录完整决策过程 |
这张表能帮助我们记住一句话:OAuth 解决的是“谁能进来”,UMA 解决的是“进来之后能做什么、做多久、能不能回收”。
3. 基于 Solid 生态的 UMA 实现
3.1 为什么选择 Solid 作为示例
在项目“Let Them: A Developer's Guide to UMA for Agents”的语境里,Solid 是一个非常合适的实现载体。Solid(Social Linked Data)是一个让用户拥有自己数据的 Web 生态标准,它天然使用了 UMA 作为授权协议。Community Solid Server(CSS)是一个开源的 Solid 服务器实现,支持 UMA 授权流程。
选择 Solid 生态的好处是:它已经帮我们把 UMA 的资源服务器、授权服务器、策略存储等环节相对完整地实现出来了,我们可以直接拿它做实验、观察协议交互,而不需要从零写一个授权服务器。
3.2 环境准备
在开始代码之前,建议准备以下环境:
- Node.js 18 以上版本
- npm 或 yarn
- Community Solid Server(CSS)最新稳定版本
- 一个支持 UMA 的客户端测试工具,例如
curl或自定义 Node.js 脚本
版本说明:Solid 和 CSS 的版本迭代较快,不同版本的配置会有细微差异。下面示例以 CSS 7.x 的常见用法为主,如果你使用的版本不同,需要适当调整。
安装 CSS:
npm install -g @solid/community-server启动服务器:
community-server默认情况下,CSS 会监听http://localhost:3000。
3.3 创建用户与受保护资源
为了演示 UMA 授权,我们先创建一个用户和一个受保护资源。
在 CSS 中,用户通过 WebID 来标识。WebID 是一个 HTTP URI,可以理解成为每个用户分配的唯一身份标识。默认端口下,你可以把用户alice的 WebID 理解为:
http://localhost:3000/alice/profile/card#me创建用户的流程通常是在服务器上执行注册请求。我们通过 curl 模拟:
curl -X POST http://localhost:3000/idp/register/ \ -H 'Content-Type: application/json' \ -d '{"email":"alice@example.com","password":"password123","name":"Alice","createWebId":true}'响应会包含 WebID、client ID 等信息。我们需要记录返回结果中的webId和clientId,后续授权流程会用到。
接着,在 Alice 的 Pod(Personal Online Data Storage,个人在线数据存储)中创建一个文件,作为受保护资源:
curl -X PUT http://localhost:3000/alice/data/calendar.ttl \ -H 'Content-Type: text/turtle' \ -d '<#event> <http://www.w3.org/2000/01/rdf-schema#label> "Team standup" .'此时,这个资源默认只能由 Alice 自己访问。如果其他客户端尝试读取,服务器会返回 401,并给出 UMA 授权服务器的地址。
4. 让 Agent 通过 UMA 访问受保护资源
4.1 交互流程模拟
现在我们进入最关键的部分:如何编写一个 Agent,让它通过 UMA 流程访问 Alice 的日历资源。
为了简化说明,我们把 Agent 设计为一个 Node.js 脚本。它需要完成以下步骤:
- 向资源服务器请求数据,收到 401 和授权服务器地址。
- 在授权服务器注册自己的客户端。
- 请求权限令牌。
- 根据资源所有者的策略获取 permission ticket。
- 用 permission ticket 换取 RPT(Requesting Party Token)。
- 携带 RPT 再次访问资源服务器。
4.2 使用 curl 观察授权流程
先用 curl 观察第一步。当我们以未授权身份访问受保护资源时,会得到:
curl -i http://localhost:3000/alice/data/calendar.ttl预期响应:
HTTP/1.1 401 Unauthorized WWW-Authenticate: UMA realm="solid", as_uri="http://localhost:3000/.well-known/uma2-configuration"注意as_uri指向的地址很重要,Agent 需要从这里读取授权服务器的配置信息,包括 token endpoint、permission endpoint、introspection endpoint 等。
接着,通过 curl 获取授权服务器配置:
curl http://localhost:3000/.well-known/uma2-configuration返回的 JSON 里可以看到:
{ "token_endpoint": "http://localhost:3000/.well-known/uma2-configuration/token", "permission_endpoint": "http://localhost:3000/.well-known/uma2-configuration/permissions", "introspection_endpoint": "http://localhost:3000/.well-known/uma2-configuration/introspect", "resource_registration_endpoint": "http://localhost:3000/.well-known/uma2-configuration/resource_registration" }4.3 使用 Node.js 编写 Agent 完整流程
下面这段代码模拟了一个“日历分析 Agent”如何通过 UMA 获取受保护资源。代码中包含了详细的注释。
// agent-uma-demo.js // 一个基于 UMA 协议访问受保护资源的 Agent 示例 const fetch = require('node-fetch'); // 配置区 const resourceUrl = 'http://localhost:3000/alice/data/calendar.ttl'; const asUri = 'http://localhost:3000/.well-known/uma2-configuration'; async function getUmaConfiguration(asUri) { const res = await fetch(asUri); if (!res.ok) { throw new Error(`无法获取 UMA 配置: ${res.status}`); } return res.json(); } // 第一步:访问资源,获取 401 和授权服务器信息 async function requestResourceWithToken(resourceUrl, accessToken) { const headers = {}; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const res = await fetch(resourceUrl, { headers }); return res; } // 第二步:在授权服务器注册客户端 async function registerClient(config) { const res = await fetch(config.client_registration_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_name: 'Calendar Analysis Agent', redirect_uris: ['http://localhost:3000/callback'], grant_types: ['urn:ietf:params:oauth:grant-type:uma-ticket'], token_endpoint_auth_method: 'none' }) }); if (!res.ok) { throw new Error(`客户端注册失败: ${res.status}`); } return res.json(); } // 第三步:请求权限令牌(Permission Token) async function requestPermissionToken(config, clientId, ticket) { const params = new URLSearchParams(); params.append('grant_type', 'urn:ietf:params:oauth:grant-type:uma-ticket'); params.append('ticket', ticket); params.append('client_id', clientId); const res = await fetch(config.token_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() }); if (!res.ok) { const err = await res.json(); throw new Error(`权限令牌请求失败: ${err.error_description || res.status}`); } return res.json(); } async function main() { // 1. 读取 UMA 配置 const config = await getUmaConfiguration(asUri); console.log('[Agent] 已获取授权服务器配置'); // 2. 未授权访问受保护资源,观察 401 响应 let res = await requestResourceWithToken(resourceUrl, null); console.log(`[Agent] 首次访问资源,状态码: ${res.status}`); const wwwAuthenticate = res.headers.get('WWW-Authenticate') || ''; console.log(`[Agent] WWW-Authenticate 头: ${wwwAuthenticate}`); if (res.status !== 401) { throw new Error('预期得到 401,但资源服务器返回了其他状态码'); } // 3. 注册客户端 const client = await registerClient(config); console.log(`[Agent] 客户端注册成功,client_id: ${client.client_id}`); // 注意:真实场景中,此时授权服务器会通知资源所有者审批。 // 在本演示中,我们假设请求方已经注册并等待策略决策。 // 4. 这里的 permission ticket 应由授权服务器在策略决策后返回。 // 在纯 UMA 流程中,agent 可能需要在 permission endpoint 上轮询或等待回调。 // 由于不同授权服务器实现差异较大,这里用一个模拟 ticket 说明完整流程。 const mockTicket = 'example-permission-ticket-123456'; // 5. 使用 ticket 换取 RPT const tokenResult = await requestPermissionToken(config, client.client_id, mockTicket); console.log(`[Agent] 已获取 RPT: ${tokenResult.access_token}`); // 6. 携带 RPT 再次访问受保护资源 res = await requestResourceWithToken(resourceUrl, tokenResult.access_token); console.log(`[Agent] 携带 RPT 访问资源,状态码: ${res.status}`); if (res.ok) { const body = await res.text(); console.log('[Agent] 资源内容:'); console.log(body); } } main().catch((err) => { console.error('[Agent] 执行失败:', err.message); process.exit(1); });这段代码的逻辑顺序是完整的,但需要注意两个关键点:
- 在真实 UMA 流程中,permission ticket 不会凭空产生。Agent 在收到 401 后,需要向授权服务器的 permission endpoint 发起请求,并携带资源 ID、所需权限范围等参数。授权服务器需要结合资源所有者的策略来决定是否发放 ticket。
- 由于不同授权服务器的策略决策时机不同,很多实现会采用异步审批。Agent 要么轮询 ticket 状态,要么通过回调通知获知审批结果。在 Solid + CSS 的实现中,这部分流程通常与用户的 WebID 和登录状态绑定,实际落地时要做更细致的适配。
4.4 运行与验证
执行 Node.js 脚本:
node agent-uma-demo.js如果配置正确,你应该能看到类似下面的日志:
[Agent] 已获取授权服务器配置 [Agent] 首次访问资源,状态码: 401 [Agent] WWW-Authenticate 头: UMA realm="solid", as_uri="http://localhost:3000/.well-known/uma2-configuration" [Agent] 客户端注册成功,client_id: ... [Agent] 已获取 RPT: ... [Agent] 携带 RPT 访问资源,状态码: 200 [Agent] 资源内容: <#event> <http://www.w3.org/2000/01/rdf-schema#label> "Team standup" .当然,由于不同版本授权服务器的实现差异,这一个示例的某些步骤可能需要调整。比如某些版本会要求先进行资源注册,将资源 ID 与授权服务器绑定;某些版本要求客户端在请求中附带resource_id和scope。这些都属于正常版本差异。
5. Agent 集成 UMA 的几种模式
5.1 短时任务模式(One-shot Task)
适用于简单的、执行一次就结束的 Agent 任务。流程:Agent 收到 401 → 请求权限 → 获得 RPT → 访问资源 → 任务结束。
这种模式实现简单,但每次任务都需要走一遍授权流程。如果 Agent 需要连续访问多个资源,性能会比较差。
5.2 长时运行模式(Long-running Session)
适用于需要长时间运行、需要多次访问资源的 Agent。典型例子是“持续监控某个网盘目录并自动归档文件”的 Agent。
这种模式下,Agent 可以在第一次访问时通过 UMA 获取 RPT,并配合 Refresh Token 来维护访问能力。当资源所有者撤销权限时,Refresh Token 会失效,Agent 下一次请求时会收到insufficient_scope或 401,从而停止操作。
5.3 策略托管模式(Policy-managed Agent)
适用于面向企业的 Agent 平台。平台方作为授权服务器,统一管理所有 Agent 的权限策略。Agent 本身不需要关心具体授权逻辑,只需携带平台提供的 Token 去访问资源。
这种模式对开发者的要求最高,需要对 UMA 策略引擎有深入理解,但对业务方来说最友好——用户只需要在统一控制台上管理权限即可。
6. 实战中常见问题与排查思路
6.1 访问资源时返回 401,但没有WWW-Authenticate头
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 返回 401,但响应头里没有 UMA 信息 | 资源服务器没有启用 UMA 支持 | 检查资源服务器配置,确认是否开启 UMA 元数据端点 |
返回 401,WWW-Authenticate头格式异常 | 授权服务器地址拼接错误 | 确认as_uri可以直接访问,并返回有效 JSON |
| 客户端注册成功,但获取不到 permission ticket | 策略引擎要求资源所有者先审批 | 检查授权服务器后台,确认审批流是否完成 |
| 携带 RPT 访问资源仍然 401 | RPT 过期或资源服务器已缓存策略 | 检查 RPT 有效期,必要时重新走授权流程 |
| 访问权限范围过大或过小 | 授权服务器策略配置不当 | 使用 JSON 策略描述精确到 resource 和 scope 粒度 |
6.2 调试 UMA 流程的实用技巧
抓包分析是最有效的调试方式。可以使用curl -i观察完整的响应头,也可以使用浏览器开发者工具的 Network 面板查看请求细节。对于 Agent 场景,更推荐在代码里增加日志输出,把每一步的状态码、响应头和返回体都打印出来。
另外,可以在授权服务器端查看审计日志。多数 UMA 授权服务器会记录每次策略决策的结果、时间、请求方信息,这是排查“为什么授权失败”的最直接途径。
# 查看授权服务器日志(CSS 示例) community-server --loggingLevel debug6.3 授权服务器和资源服务器混淆问题
UMA 场景下,授权服务器和资源服务器往往是两套服务。很多初学者会把两个地址混淆,导致明明在授权服务器上配置好了策略,但资源服务器依然返回 401。记住:资源服务器的职责是“验证访问令牌”,授权服务器的职责是“颁发访问令牌”。两边通过introspection_endpoint来同步令牌状态。
7. UMA 与现代 Agent 架构的结合方向
7.1 从模型到权限的闭环
在 LLM 驱动的 Agent 中,模型本身不直接决定权限边界。一个模型可能调用工具,工具再去访问数据。如果想让 Agent 的能力既强大又可控,就需要在“工具调用层”之前加一道权限校验,而 UMA 正好提供了这种校验的标准协议。
我们可以这样设计:
用户指令 ↓ Agent Orchestrator ↓ 工具调用前 权限决策层(调用 UMA 授权服务器) ↓ 授权通过 工具函数 → 资源服务器这个设计的好处是:Agent 的“行为边界”不再由 Prompt 暗示,不再由代码里写死的 API Key 决定,而是由一套用户可以实时查看、修改的策略来约束。这正好回应当前社区里关于 deep agents interrupt、self-improving agents 等热词背后的核心担忧——Agent 能力越强,权限和边界就越重要。
7.2 和 “Self-Improving Agents” 的潜在冲突
近期社区在讨论 “Self-Improving Agents”(自我改进智能体)和 “Experience-based Self-to-Meta Evolution” 这类主题。自我改进意味着 Agent 会积累经验、调整策略、优化工具调用方式。如果 Agent 的权限也是“自我调整”的,那将是非常危险的。
一个合理的方案是:Agent 可以自我调整“任务规划”,但绝不能自我调整“授权策略”。授权策略必须由资源所有者或平台管理员控制。UMA 的策略引擎天然支持这种分离——Agent 是 RqP,资源所有者才是策略的制定者。这样的架构既能让 Agent 保持智能进化的优势,又不会破坏权限安全边界。
7.3 Efficient Agents 与授权性能
面向高效 Agent(toward efficient agents)的方向,开发者还需要关注授权性能。如果 Agent 每次调用工具都要走一次完整的 UMA 流程,延迟会明显增加。实际工程中,可以用以下手段优化:
- 为常用资源提前申请 RPT,并缓存到过期时间。
- 在 Agent 会话范围内复用 RPT。
- 使用分布式缓存保存 RPT 与资源 ID 的映射。
- 对低频敏感数据使用实时策略评估,对高频非敏感数据使用降级缓存策略。
8. 最佳实践与工程建议
8.1 不要用长期令牌
很多开发者习惯把长期有效的 Access Token 写在配置文件里。在 Agent 场景下,这是非常危险的做法。Agent 运行时间越长,被攻击面越大。UMA 提供的短期 RPT + Refresh Token 轮换机制更适合 Agent。
8.2 明确权限边界
给 Agent 授权时,遵循最小权限原则。不要一次性授权整个 Pod 的读权限,而是根据任务需求,精确到具体路径和具体操作。
例如,日历分析 Agent 只需要「读取 calendar.ttl」的权限,没必要给它「读取整个 /data/ 目录」的权限。UMA 的策略引擎允许你把资源的访问控制精确到 URI 和 scope 级别。
8.3 所有授权请求必须可见、可撤销
Agent 代表用户执行任务时,用户必须能随时看到当前有哪些 Agent 在访问自己的数据,以及它们被授予了哪些权限。一旦发现异常,用户可以一键撤销。
Solid 生态中的一些控制台应用已经实现了这种可视化管理。如果你是自研 Agent 平台,建议在界面上提供一个“已授权 Agent 列表”,并支持用户撤回授权。
8.4 记录完整的决策链路
合规审计中,需要回答三个问题:
- 谁(Which Agent / Which User)请求了访问?
- 什么时间(When)发起了请求?
- 授权服务器基于什么策略(Why)做了决定?
UMA 授权服务器天然适合记录这些信息。在工程实现时,务必保留策略决策日志,并与 Agent 的任务执行日志做关联,这样在出现问题时才能快速定位。
8.5 使用标准库和认证库,不手动解析协议
虽然 UMA 基于 JSON 和 HTTP,但手动解析协议头很容易出错。推荐使用成熟的 OAuth / UMA 库,例如 oidc-client、oauth4webapi、@solid/access-token-verifier 等。自己实现协议细节只适合学习,不适合生产环境。
9. 总结
UMA 为 Agent 开发提供了一套成熟、可审计、可动态调整的授权方案。它解决了传统 OAuth 2.0 在动态授权、策略撤销、细粒度控制方面的不足,也正好回应了当前 Agent 快速落地时遇到的权限边界问题。
本文从 UMA 的核心概念讲起,梳理了参与方、授权流程和与 OAuth 2.0 的差异;接着基于 Solid 生态给出了一个可运行的 Agent 访问受保护资源的示例;然后讨论了 Agent 集成 UMA 的三种模式,以及常见问题的排查思路。最后,我们分析了如何把 UMA 与自我改进型 Agent、高效 Agent 方向结合,并给出了工程化的最佳实践。
如果你正在开发 Agent 应用,下一步可以尝试把 UMA 集成到你的工具调用链路里,哪怕先用一个简单的文件服务做实验。等你跑通完整流程后,再逐步扩展到日历、邮箱、网盘等真实数据源,你会更深刻地理解“授权”这两个字在 Agent 时代的重量。