OAuth 可选权限来了:让用户决定 Agent 到底能做什么

2026-09-02 28 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

Cloudflare 为 OAuth 增加了“可选 scope”能力:客户端开发者可以标记哪些权限允许用户在授权页面主动取消。这个变化针对的是一个越来越常见的问题,尤其是在 MCP server 和 AI agent 场景中:Agent 可能需要请求它潜在会使用的全部权限,但用户并不一定愿意一次性授予所有能力。

从“全部授权”到“按能力选择”

传统 OAuth 授权通常把 scope 视为一个整体。客户端在授权请求中列出 read:documentswrite:documentsdelete:documents 等权限,用户要么全部同意,要么拒绝整个请求。

部分同意并不是新概念,很多 OAuth 实现已经允许用户取消某些权限。但 Cloudflare 这次强调的是另一层控制:由客户端开发者明确声明哪些 scope 可以被取消,哪些 scope 必须保留

这能解决两个实际问题:

  • 核心能力可以设为必选,避免客户端拿到一个无法正常工作的授权结果。
  • 高风险或低频能力可以设为可选,让用户根据实际信任程度决定是否授予。

例如,一个文档 Agent 可能需要读取文档才能回答问题,但只有在用户要求“帮我修改文档”时才需要写权限。删除权限则可能永远不应该默认开启。

为什么 MCP server 更需要这项能力

MCP server 通常会向 Agent 暴露一组工具。Agent 在初始化或连接阶段,可能需要请求这些工具未来可能使用的权限集合,而不是只请求当前对话立即需要的权限。

这会形成典型的权限膨胀:

Agent 可能调用:
- search_documents
- read_document
- update_document
- delete_document

实际用户问题:
“帮我找出上个月的设计文档。”

如果授权流程只能整体批准,用户可能被迫同时授予搜索、读取、更新和删除权限。可选 scope 允许服务端把权限表达成更接近实际风险的结构:

必选:documents:read
可选:documents:write
可选:documents:delete

用户仍然可以拒绝整个客户端,但也可以保留只读能力,关闭写入和删除能力。对于连接第三方 SaaS 的 Agent,这种粒度比一个笼统的“访问你的账户”更容易解释,也更容易获得用户信任。

一个可改造的授权请求示例

下面的命令是一个通用 OAuth 示例。实际接入时,需要把授权端点、客户端 ID、重定向地址和 scope 名称替换成目标服务的配置。optional_scopes 的具体编码方式取决于 OAuth 服务商的协议;如果服务商提供专用参数,应优先使用其官方格式。

curl -G 'https://auth.example.com/oauth/authorize' \
  --data-urlencode 'response_type=code' \
  --data-urlencode 'client_id=YOUR_CLIENT_ID' \
  --data-urlencode 'redirect_uri=https://app.example.com/oauth/callback' \
  --data-urlencode 'scope=documents:read documents:write documents:delete' \
  --data-urlencode 'optional_scope=documents:write documents:delete' \
  --data-urlencode 'state=RANDOM_CSRF_VALUE'

这个请求表达了三件事:

  1. documents:read 是客户端运行所需的核心权限。
  2. documents:writedocuments:delete 可以由用户取消。
  3. state 仍然必须是不可预测且与用户会话绑定的值,用来防止 OAuth 回调被 CSRF 攻击利用。

如果平台的授权页面返回最终同意的 scope,服务端应当把它当作真实权限边界,而不是假设所有请求的 scope 都已获得。

一个最小的 Python 处理逻辑可以这样写:

from urllib.parse import parse_qs

REQUIRED_SCOPES = {"documents:read"}
OPTIONAL_SCOPES = {"documents:write", "documents:delete"}


def validate_granted_scopes(scope_value: str) -> set[str]:
    granted = set(scope_value.split())
    unknown = granted - REQUIRED_SCOPES - OPTIONAL_SCOPES
    if unknown:
        raise ValueError(f"Unexpected scopes: {sorted(unknown)}")
    missing = REQUIRED_SCOPES - granted
    if missing:
        raise PermissionError(f"Required scopes were not granted: {sorted(missing)}")
    return granted


def capabilities_for_token(token_response: dict) -> dict[str, bool]:
    scopes = validate_granted_scopes(token_response.get("scope", ""))
    return {
        "can_read": "documents:read" in scopes,
        "can_write": "documents:write" in scopes,
        "can_delete": "documents:delete" in scopes,
    }


if __name__ == "__main__":
    token = {
        "scope": "documents:read documents:write"
    }
    print(capabilities_for_token(token))

运行后,应用可以根据实际 scope 关闭写入和删除工具,而不是等到工具调用失败后才返回错误。对于 MCP server,可以把这些能力映射到工具注册或调用前检查中:没有 documents:delete 就不要向 Agent 暴露删除工具,或者在调用时明确返回权限不足。

开发者需要重新设计什么

可选 scope 不是把权限列表加一个标记就结束了。客户端还需要让每种授权结果都能正常工作。

工具和界面要适配部分授权。 如果用户只授予读取权限,应用仍应提供搜索和查看功能,而不是显示一个模糊的“连接失败”。

高风险能力应保持可选。 写入、删除、发送消息、执行付款等权限通常比读取权限更敏感。把它们设置为可取消,可以降低一次性授权过宽的问题。

服务端必须以 token 返回的 scope 为准。 不能仅依据初始请求中的 scope 决定能力。OAuth 提供方可能返回一个被用户删减后的 scope 集合,资源服务器也可能在后续刷新流程中改变授权状态。

权限名称要能被用户理解。 documents:writeaccess_level_7 更容易解释,但授权页面还需要进一步显示“修改文档”“删除文档”等面向用户的描述。对于 Agent,权限说明应直接对应它可以执行的动作。

落地检查清单

采用可选 scope 时,可以检查以下事项:

  • 把最小可运行能力定义为必选 scope。
  • 将写入、删除和外部副作用操作拆成独立的可选 scope。
  • 在回调和 token 处理逻辑中校验实际授予的 scope。
  • 根据最终权限动态注册或禁用 MCP 工具。
  • 为“只读授权”“读写授权”和“拒绝必选权限”分别编写测试。
  • 在授权页面清楚解释用户取消每个可选权限后的影响。
  • 继续校验 state、重定向地址、PKCE 和 token 存储安全性。

Cloudflare 的变化把 OAuth 的“部分同意”推进了一步:权限是否可以被取消,不再完全由授权页面自行决定,而是成为客户端能力设计的一部分。对于 MCP server 和 Agent,这意味着开发者可以把“它可能做什么”拆成更细的授权边界,让用户保留核心功能,同时拒绝不必要的高风险操作。


相关推荐