Frame.io Python SDK — 身份验证指南

本指南介绍如何使用 Frame.io Python SDK (frameio) 通过 Frame.io API 进行身份验证。 Frame.io V4 API 使用 Adobe Identity Management Service (IMS),Adobe 的 OAuth 2.0 身份平台。 这是面向 Python 开发者的独立参考。 下方的所有代码示例和流程仅适用于 frameio 包。


Python SDK 中的身份验证类型

Python SDK 支持四种身份验证选项:

方法用例是否需要用户交互?是否需要客户端密钥?
静态令牌快速脚本、测试,或者您已拥有令牌
服务器到服务器后端服务、定时任务、自动化
Web 应用程序服务器端应用程序(Flask、Django、FastAPI)
SPA (PKCE)浏览器应用程序、CLI 或任何无法存储密钥的应用程序
服务器到服务器方式让您的应用程序以服务帐户身份运行,无需用户交互。此方式仅适用于通过 Adobe Admin Console 管理的 Frame.io V4 帐户。Web 应用程序SPA 方式让您的应用程序以特定用户的身份运行。两者底层都使用 Adobe IMS:用户授权您的应用程序后,SDK 会将生成的代码交换为令牌。Python SDK 为您处理 IMS 的 /authorize/v2/token/v3 流程。对于 Web 应用程序,您需要客户端密钥;对于 SPA,则改用 PKCE

Adobe 的原生应用程序凭据需要自定义 URI 方案处理程序(例如 adobe+<hash>://…</hash>),这些处理程序会在操作系统层面拦截重定向。Python 没有注册此类处理程序的标准方法,因此 Python SDK 不提供 NativeAppAuth 类。对于需要用户交互的 Python 应用程序,请使用 WebAppAuth 并搭配本地回调服务器(例如 Flask 或 FastAPI)。对于非交互式工作负载,请使用 ServerToServerAuth


服务帐户用户

使用服务器到服务器的身份验证时,您的应用程序充当服务帐户用户,这是一种可以代表服务执行操作的独特帐户类型。 这些内容在 Frame.io 中对其他用户可见:当服务帐户执行操作时,其名称会显示在 UI 中。 您可以通过 Adobe Admin ConsoleDeveloper Console 授予和撤销服务帐户访问权限。 服务帐户名称通过 Frame.io UI 进行管理。 默认情况下,您的第一个 S2S 连接名为 Service Account User,第二个名为 Service Account User 2,以此类推。


快速入门

前提条件

  1. Adobe Developer Console 提供的 凭据
  • 客户端 ID — 所有 OAuth 流均需要 - 客户端密钥 — 服务器到服务器的流和 Web 应用程序流需要 - 重定向 URI — Web 应用程序流和 SPA 流需要;必须注册在 Adobe 项目中
  1. 安装 SDK:
$pip install frameio

选择方法

  • 无用户参与? 使用服务器到服务器 (ServerToServerAuth)。
  • 有用户参与且您可以存储密钥? 使用 Web 应用程序 (WebAppAuth)。
  • 有用户参与但您无法存储密钥? 使用 SPA (SPAAuth)。

访问令牌

如果您已有访问令牌(来自其他 OAuth 系统或之前的交换,例如通过我们的 API Explorer获得),那么您可以直接传递:

1from frameio import Frameio
2
3client = Frameio(token="YOUR_ACCESS_TOKEN")

这是最简单的方法,但令牌最终会过期,SDK 不会为您刷新令牌。

旧版开发者令牌

对于尚未通过 Adobe Admin Console 进行管理的 V4 迁移帐户,您可以继续使用来自 Frame.io 开发者网站的旧版开发者令牌。 您必须包含 x-frameio-legacy-token-auth 标头并将其设置为 true

1from frameio import Frameio
2
3client = Frameio(
4 token="YOUR_LEGACY_DEVELOPER_TOKEN",
5 headers={"x-frameio-legacy-token-auth": "true"},
6)

旧版开发者令牌不会过期,但它们是一种过渡机制。 对于新的集成和生产工作负载,我们推荐使用下面的其中一个 OAuth 2.0 流。 有关详细信息,请参阅迁移指南


服务器到服务器(客户端凭据)

将此用于需要 Frame.io 访问权限且无需用户交互的后端服务和脚本。 此流仅适用于通过 Adobe Admin Console 管理的 Frame.io V4 帐户。 您的应用程序以服务帐户用户的身份进行身份验证,无需人工干预。

1 from frameio import Frameio
2 from frameio.auth import ServerToServerAuth
3
4 auth = ServerToServerAuth(
5 client_id="YOUR_CLIENT_ID",
6 client_secret="YOUR_CLIENT_SECRET",
7 )
8
9 client = Frameio(token=auth.get_token)

就是这样。 auth.get_token 是 SDK 在每个请求中都会调用的一个可调用对象。 如果当前令牌仍然有效,它就会立即返回。 如果即将过期,它会先获取一个新令牌,过程完全透明。

工作原理

您的客户端凭据(客户端 ID + 密钥)永不过期。 您只需出于安全维护目的手动轮换它们即可。 S2S 可为您提供实际上永久、不间断的 API 访问权限,无需手动干预。

底层原理:

  1. 对于第一次 API 调用,get_token 使用 client_credentials 授权凭证从 Adobe IMS 请求新的访问令牌。
  2. 令牌会缓存在内存中。 单个访问令牌会过期(通常为 24 小时),但这会为您自动处理。
  3. 当缓存的令牌在刷新缓冲时间内时(默认:过期时间前 60 秒),SDK 会使用相同的客户端凭据自动获取新的令牌。
  4. 不涉及刷新令牌。 客户端凭据本身就是长期有效的密钥,始终可用于生成新的访问令牌。

显式身份验证

如果您想主动获取令牌(例如,在启动时因错误凭据而快速失败):

1auth = ServerToServerAuth(client_id="...", client_secret="...")
2auth.authenticate() # raises AuthenticationError if credentials are invalid
3client = Frameio(token=auth.get_token)

Web 应用程序(授权代码)

将此用于用户使用其 Adobe ID 登录的服务器端应用程序。 此流需要客户端密钥,必须安全地将其存储在您的服务器上。

1

将用户重定向到 Adobe IMS

1 auth.refresh() # fetches a new access token using the refresh token
2

处理回调

当 Adobe IMS 将用户重定向回您的 redirect_uri 时,提取 codestate 参数。验证状态是否与您存储的内容匹配,然后将代码交换为令牌:

1 auth.refresh() # fetches a new access token using the refresh token

这会将授权代码交换为访问令牌和刷新令牌,并将两者都存储在内部。

3

使用客户端

1 from frameio import Frameio
2
3 client = Frameio(token=auth.get_token)

就是这样。 从现在开始,get_token 会自动管理令牌生命周期。 当访问令牌即将过期时,SDK 会使用刷新令牌获取新令牌。 无需用户交互。

完整的 Flask 示例

1import secrets
2from flask import Flask, redirect, request, session
3from frameio import Frameio
4from frameio.auth import WebAppAuth
5
6app = Flask(__name__)
7app.secret_key = secrets.token_bytes(32)
8
9auth = WebAppAuth(
10 client_id="YOUR_CLIENT_ID",
11 client_secret="YOUR_CLIENT_SECRET",
12 redirect_uri="http://localhost:5000/callback",
13)
14
15@app.route("/login")
16def login():
17 state = secrets.token_urlsafe(32)
18 session["oauth_state"] = state
19 return redirect(auth.get_authorization_url(state=state))
20
21@app.route("/callback")
22def callback():
23 if request.args.get("state") != session.pop("oauth_state", None):
24 return "Invalid state parameter", 403
25
26 auth.exchange_code(code=request.args["code"])
27
28 client = Frameio(token=auth.get_token)
29 accounts = client.accounts.index()
30 return f"Authenticated — {len(accounts.data)} account(s) accessible."

单页面应用程序 / PKCE(授权代码 + PKCE)

将其用于无法安全存储客户端密钥的基于浏览器的应用程序、桌面应用程序或 CLI 工具。 此流使用 PKCE (RFC 7636) 来保护授权代码交换。

1

生成授权 URL

1 from frameio.auth import SPAAuth
2
3 auth = SPAAuth(
4 client_id="YOUR_CLIENT_ID",
5 redirect_uri="https://yourapp.com/callback",
6 )
7
8 import secrets
9 state = secrets.token_urlsafe(32)
10
11 result = auth.get_authorization_url(state=state)
12 # result.url -> redirect the user here
13 # result.code_verifier -> store this securely until the callback

get_authorization_url 返回一个 AuthorizationUrlResult,其中包含完整 URL(嵌入 PKCE code_challenge)以及您在下一步中需要的 code_verifier

2

使用验证器交换代码

当用户被重定向回来时:

1 auth.exchange_code(
2 code="CODE_FROM_CALLBACK",
3 code_verifier=result.code_verifier,
4 )
3

使用客户端

1 from frameio import Frameio
2
3 client = Frameio(token=auth.get_token)

就是这样。 刷新操作与 Web 应用程序相同 — SDK 会自动使用刷新令牌。 区别在于,刷新期间不会发送客户端密钥,因为 SPA 流专为公共客户端而设计。


异步用法

每个身份验证类都有一个异步对应项,以 Async 为前缀。 上述代码示例包含适用的 SyncAsync 选项卡。

同步异步
ServerToServerAuthAsyncServerToServerAuth
WebAppAuthAsyncWebAppAuth
SPAAuthAsyncSPAAuth
API 完全相同。 get_authorization_url 保持同步(无 I/O),而 exchange_coderefreshrevokeget_token 都是 async。 将异步类与 AsyncFrameio 一起使用。

手动令牌刷新

对于 Web 应用程序和 SPA 流,SDK 通过 get_token 自动刷新令牌。 如果您需要显式控制,可以直接调用 refresh()

1 auth.refresh() # fetches a new access token using the refresh token

当您希望在关键操作之前强制刷新而不是依赖自动刷新缓冲区时,这样做非常有用。


令牌持久性

所有身份验证类都支持 export_tokens()import_tokens() 来在重新启动时持久化令牌状态。 对于 Web 应用程序和 SPA 流程,这尤其重要,因为访问令牌和刷新令牌默认保存在内存中 — 如果您的应用程序重新启动,用户需要重新进行身份验证,除非您对这些令牌进行持久化。 对于服务器到服务器,持久性是可选的(客户端凭据始终可以生成新令牌),但导入缓存的令牌可以避免启动时出现额外的往返。

导出和导入

1# After exchange_code(), save the token state
2token_data = auth.export_tokens()
3# token_data is a dict: {"access_token": "...", "refresh_token": "...", "expires_at": 1234567890.0}
4# Save it to your database, file, or secret store
5
6# On next startup, restore it
7auth.import_tokens(token_data)
8client = Frameio(token=auth.get_token)
9# The SDK will automatically refresh if the token is near expiry

使用 on_token_refreshed 自动持久化

要在每次刷新时自动持久化令牌,请使用 on_token_refreshed 回调:

1import json
2from pathlib import Path
3
4TOKEN_FILE = Path("tokens.json")
5
6def save_tokens(tokens: dict):
7 TOKEN_FILE.write_text(json.dumps(tokens))
8
9auth = WebAppAuth(
10 client_id="...",
11 client_secret="...",
12 redirect_uri="...",
13 on_token_refreshed=save_tokens,
14)
15
16# On startup, restore if available
17if TOKEN_FILE.exists():
18 auth.import_tokens(json.loads(TOKEN_FILE.read_text()))

该回调接收与 export_tokens() 相同的字典结构,并在每次成功刷新令牌后触发。对于异步类,on_token_refreshed 可以是常规函数也可以是 async 函数。 两者均受支持。


撤销令牌

要注销用户并通过 Adobe IMS 使其令牌失效:

1auth.revoke()

这会向 Adobe IMS 发出尽力撤销访问令牌和刷新令牌的请求,然后清除所有本地令牌状态。 撤销后,用户将需要重新进行身份验证。

对于异步类,请使用 await auth.revoke()


错误处理

所有身份验证错误都继承自 FrameioAuthError,因此您可以广泛捕获它们或处理特定用例:

1from frameio.auth import (
2 FrameioAuthError,
3 AuthenticationError,
4 TokenExpiredError,
5 ConfigurationError,
6 NetworkError,
7 RateLimitError,
8)
9
10try:
11 auth.exchange_code(code="...")
12except TokenExpiredError:
13 # The refresh token has expired; redirect the user to sign in again
14 pass
15except AuthenticationError as e:
16 # Token exchange failed
17 print(f"Error: {e.error_code} - {e.error_description}")
18except NetworkError:
19 # Timeout or connection failure (after retries)
20 pass
21except RateLimitError as e:
22 # 429 from Adobe IMS; retry after e.retry_after seconds
23 pass
24except FrameioAuthError:
25 # Catch-all for any other auth error
26 pass

错误参考

例外引发的时间
ConfigurationError配置缺失或无效(例如,client_id 为空,非 HTTPS 重定向 URI)
AuthenticationErrorAdobe IMS 拒绝令牌交换或刷新(具有 .error_code.error_description
TokenExpiredError刷新令牌本身已过期;用户必须重新进行身份验证
NetworkErrorHTTP 超时或完成所有重试后仍出现连接故障
RateLimitErrorAdobe IMS 返回 429;查看 .retry_after 获取退避指导
PKCEErrorPKCE 验证失败(SPA 流)

在生产中处理过期的刷新令牌

对于 Web 应用程序和 SPA 流,刷新令牌最终会过期。当这种情况发生时,get_token 会引发 TokenExpiredError。 您应该捕获这个错误,并再次将用户重定向到授权流。

1from frameio.auth import TokenExpiredError
2
3try:
4 client = Frameio(token=auth.get_token)
5 assets = client.files.list(project_id="...")
6except TokenExpiredError:
7 # Clear persisted tokens and redirect user to login
8 auth.revoke()
9 return redirect("/login")

配置参考

所有授权类都接受这些可选参数:

参数默认描述
scopes特定于流的默认值以空格分隔的 OAuth 权限范围。 S2S 默认为 openid AdobeID frame.s2s.all;面向用户的流默认为 openid email profile offline_access additional_info.roles
ims_base_urlhttps://ims-na1.adobelogin.comAdobe IMS 基础 URL。 针对暂存或非生产环境的覆盖设置。
http_client自定义 httpx。用于代理、mTLS 或连接池的客户端(或 httpx.AsyncClient)。
timeout30令牌端点调用的 HTTP 请求超时时间(以秒为单位)。
max_retries2瞬态故障(5xx、超时)的最大重试次数。 速率限制重试 (429) 单独跟踪。
refresh_buffer60令牌过期时间前触发主动刷新的秒数。
on_token_refreshed每次成功刷新令牌后触发的回调。 接收包含 access_tokenrefresh_tokenexpires_at 的字典。

暂存环境

通过重写 ims_base_url 指向暂存的 Adobe IMS 实例。 如果您需要以编程方式引用生产值,SDK 还可以导出 DEFAULT_IMS_BASE_URL (https://ims-na1.adobelogin.com)。

1auth = ServerToServerAuth(
2 client_id="...",
3 client_secret="...",
4 ims_base_url="https://ims-na1-stg1.adobelogin.com",
5)

自定义 HTTP 客户端

对于代理支持或自定义 TLS 配置:

1import httpx
2
3http_client = httpx.Client(
4 proxy="http://corporate-proxy:8080",
5 verify="/path/to/custom-ca-bundle.pem",
6)
7
8auth = ServerToServerAuth(
9 client_id="...",
10 client_secret="...",
11 http_client=http_client,
12)

线程安全

同步身份验证类是完全线程安全的。 当多个线程同时调用 get_token 且需要刷新时,只有一个线程会执行刷新。 其他线程会等待并接收相同结果。 无需外部锁定。 异步类使用 asyncio.Lock 提供相同的保证,对单个事件循环中的并发协程是安全的。