> This page is for 平台, version V4 (default).
> For other versions, use one of these documentation indexes:
> - V4 (default): https://next.developer.frame.io/platform/v4/llms.txt
> - V4 实验版: https://next.developer.frame.io/platform/v4-experimental/llms.txt
> - 旧版: https://next.developer.frame.io/platform/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://next.developer.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://next.developer.frame.io/_mcp/server.

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

本指南介绍如何使用 **Frame.io Python SDK** (`frameio`) 通过 Frame.io API 进行身份验证。 Frame.io V4 API 使用 [Adobe Identity Management Service (IMS)](https://developer.adobe.com/developer-console/docs/guides/authentication/)，Adobe 的 OAuth 2.0 身份平台。 这是面向 Python 开发者的独立参考。 下方的所有代码示例和流程仅适用于 `frameio` 包。

---

## Python SDK 中的身份验证类型

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

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

> **Note**
>
> Adobe 的[原生应用程序凭据](https://developer.adobe.com/developer-console/docs/guides/authentication/UserAuthentication/implementation/#oauth-native-app-credential)需要自定义 URI 方案处理程序（例如 `adobe+<hash>://…</hash>`），这些处理程序会在操作系统层面拦截重定向。Python 没有注册此类处理程序的标准方法，因此 Python SDK 不提供 `NativeAppAuth` 类。对于需要用户交互的 Python 应用程序，请使用 `WebAppAuth` 并搭配本地回调服务器（例如 Flask 或 FastAPI）。对于非交互式工作负载，请使用 `ServerToServerAuth`。

---

## 服务帐户用户

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

> **Info**
>
> 有关更多信息，请参阅 [使用 Frame.io 服务器到服务器的支持完成自动化设置](https://helpx.adobe.com/enterprise/using/automate-using-frame-io.html)。

---

## 快速入门

### 前提条件

1. [Adobe Developer Console](https://developer.adobe.com/console) 提供的 **凭据**：

* **客户端 ID** — 所有 OAuth 流均需要 - **客户端密钥** — 服务器到服务器的流和 Web 应用程序流需要 - **重定向 URI** — Web 应用程序流和 SPA 流需要；必须注册在 Adobe 项目中

2. **安装 SDK：**

```bash
pip install frameio
```

### 选择方法

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

---

## 访问令牌

如果您已有访问令牌（来自其他 OAuth 系统或之前的交换，例如通过我们的 [API Explorer](/platform/api-reference/accounts/index?explorer=true)获得），那么您可以直接传递：

```python
from frameio import Frameio

client = Frameio(token="YOUR_ACCESS_TOKEN")
```

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

### 旧版开发者令牌

对于尚未通过 [Adobe Admin Console](https://adminconsole.adobe.com/) 进行管理的 V4 迁移帐户，您可以继续使用来自 [Frame.io 开发者网站](https://developer.frame.io/app/tokens)的旧版开发者令牌。 您必须包含 `x-frameio-legacy-token-auth` 标头并将其设置为 `true`：

```python
from frameio import Frameio

client = Frameio(
    token="YOUR_LEGACY_DEVELOPER_TOKEN",
    headers={"x-frameio-legacy-token-auth": "true"},
)
```

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

---

## 服务器到服务器（客户端凭据）

将此用于需要 Frame.io 访问权限且无需用户交互的后端服务和脚本。 此流仅适用于通过 [Adobe Admin Console](https://adminconsole.adobe.com/) 管理的 Frame.io V4 帐户。 您的应用程序以[服务帐户用户](#service-account-users)的身份进行身份验证，无需人工干预。

#### 同步

```python
    from frameio import Frameio
    from frameio.auth import ServerToServerAuth

    auth = ServerToServerAuth(
        client_id="YOUR_CLIENT_ID",
        client_secret="YOUR_CLIENT_SECRET",
    )

    client = Frameio(token=auth.get_token)
```

#### 异步

```python
    from frameio import AsyncFrameio
    from frameio.auth import AsyncServerToServerAuth

    auth = AsyncServerToServerAuth(
        client_id="YOUR_CLIENT_ID",
        client_secret="YOUR_CLIENT_SECRET",
    )

    client = AsyncFrameio(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. 不涉及刷新令牌。 客户端凭据本身就是长期有效的密钥，始终可用于生成新的访问令牌。

### 显式身份验证

如果您想主动获取令牌（例如，在启动时因错误凭据而快速失败）：

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

---

## Web 应用程序（授权代码）

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

#### 将用户重定向到 Adobe IMS

#### 同步

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

#### 异步

```python
    await auth.refresh()
```

#### 处理回调

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

#### 同步

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

#### 异步

```python
    await auth.refresh()
```

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

#### 使用客户端

#### 同步

```python
        from frameio import Frameio

        client = Frameio(token=auth.get_token)
```

#### 异步

```python
        from frameio import AsyncFrameio

        client = AsyncFrameio(token=auth.get_token)
```

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

### 完整的 Flask 示例

```python
import secrets
from flask import Flask, redirect, request, session
from frameio import Frameio
from frameio.auth import WebAppAuth

app = Flask(__name__)
app.secret_key = secrets.token_bytes(32)

auth = WebAppAuth(
    client_id="YOUR_CLIENT_ID",
    client_secret="YOUR_CLIENT_SECRET",
    redirect_uri="http://localhost:5000/callback",
)

@app.route("/login")
def login():
    state = secrets.token_urlsafe(32)
    session["oauth_state"] = state
    return redirect(auth.get_authorization_url(state=state))

@app.route("/callback")
def callback():
    if request.args.get("state") != session.pop("oauth_state", None):
        return "Invalid state parameter", 403

    auth.exchange_code(code=request.args["code"])

    client = Frameio(token=auth.get_token)
    accounts = client.accounts.index()
    return f"Authenticated — {len(accounts.data)} account(s) accessible."
```

---

## 单页面应用程序 / PKCE（授权代码 + PKCE）

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

#### 生成授权 URL

#### 同步

```python
        from frameio.auth import SPAAuth

        auth = SPAAuth(
            client_id="YOUR_CLIENT_ID",
            redirect_uri="https://yourapp.com/callback",
        )

        import secrets
        state = secrets.token_urlsafe(32)

        result = auth.get_authorization_url(state=state)
        # result.url            -> redirect the user here
        # result.code_verifier  -> store this securely until the callback
```

#### 异步

```python
        from frameio.auth import AsyncSPAAuth

        auth = AsyncSPAAuth(
            client_id="YOUR_CLIENT_ID",
            redirect_uri="https://yourapp.com/callback",
        )

        # get_authorization_url is synchronous (no I/O needed)
        import secrets
        state = secrets.token_urlsafe(32)

        result = auth.get_authorization_url(state=state)
```

`get_authorization_url` 返回一个 `AuthorizationUrlResult`，其中包含完整 URL（嵌入 PKCE `code_challenge`）以及您在下一步中需要的 `code_verifier`。

#### 使用验证器交换代码

当用户被重定向回来时：

#### 同步

```python
        auth.exchange_code(
            code="CODE_FROM_CALLBACK",
            code_verifier=result.code_verifier,
        )
```

#### 异步

```python
        await auth.exchange_code(
            code="CODE_FROM_CALLBACK",
            code_verifier=result.code_verifier,
        )
```

#### 使用客户端

#### 同步

```python
        from frameio import Frameio

        client = Frameio(token=auth.get_token)
```

#### 异步

```python
        from frameio import AsyncFrameio

        client = AsyncFrameio(token=auth.get_token)
```

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

---

## 异步用法

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

| 同步                                                                                                                                      | 异步                        |
| --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `ServerToServerAuth`                                                                                                                    | `AsyncServerToServerAuth` |
| `WebAppAuth`                                                                                                                            | `AsyncWebAppAuth`         |
| `SPAAuth`                                                                                                                               | `AsyncSPAAuth`            |
| API 完全相同。 `get_authorization_url` 保持同步（无 I/O），而 `exchange_code`、`refresh`、`revoke` 和 `get_token` 都是 `async`。 将异步类与 `AsyncFrameio` 一起使用。 |                           |

### 手动令牌刷新

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

#### 同步

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

#### 异步

```python
    await auth.refresh()
```

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

---

## 令牌持久性

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

### 导出和导入

```python
# After exchange_code(), save the token state
token_data = auth.export_tokens()
# token_data is a dict: {"access_token": "...", "refresh_token": "...", "expires_at": 1234567890.0}
# Save it to your database, file, or secret store

# On next startup, restore it
auth.import_tokens(token_data)
client = Frameio(token=auth.get_token)
# The SDK will automatically refresh if the token is near expiry
```

### 使用 `on_token_refreshed` 自动持久化

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

```python
import json
from pathlib import Path

TOKEN_FILE = Path("tokens.json")

def save_tokens(tokens: dict):
    TOKEN_FILE.write_text(json.dumps(tokens))

auth = WebAppAuth(
    client_id="...",
    client_secret="...",
    redirect_uri="...",
    on_token_refreshed=save_tokens,
)

# On startup, restore if available
if TOKEN_FILE.exists():
    auth.import_tokens(json.loads(TOKEN_FILE.read_text()))
```

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

---

## 撤销令牌

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

```python
auth.revoke()
```

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

> **Tip**
>
> 对于异步类，请使用 `await auth.revoke()`。

---

## 错误处理

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

```python
from frameio.auth import (
    FrameioAuthError,
    AuthenticationError,
    TokenExpiredError,
    ConfigurationError,
    NetworkError,
    RateLimitError,
)

try:
    auth.exchange_code(code="...")
except TokenExpiredError:
    # The refresh token has expired; redirect the user to sign in again
    pass
except AuthenticationError as e:
    # Token exchange failed
    print(f"Error: {e.error_code} - {e.error_description}")
except NetworkError:
    # Timeout or connection failure (after retries)
    pass
except RateLimitError as e:
    # 429 from Adobe IMS; retry after e.retry_after seconds
    pass
except FrameioAuthError:
    # Catch-all for any other auth error
    pass
```

### 错误参考

| 例外                    | 引发的时间                                                        |
| --------------------- | ------------------------------------------------------------ |
| `ConfigurationError`  | 配置缺失或无效（例如，`client_id` 为空，非 HTTPS 重定向 URI）                   |
| `AuthenticationError` | Adobe IMS 拒绝令牌交换或刷新（具有 `.error_code` 和 `.error_description`） |
| `TokenExpiredError`   | 刷新令牌本身已过期；用户必须重新进行身份验证                                       |
| `NetworkError`        | HTTP 超时或完成所有重试后仍出现连接故障                                       |
| `RateLimitError`      | Adobe IMS 返回 429；查看 `.retry_after` 获取退避指导                    |
| `PKCEError`           | PKCE 验证失败（SPA 流）                                             |

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

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

```python
from frameio.auth import TokenExpiredError

try:
    client = Frameio(token=auth.get_token)
    assets = client.files.list(project_id="...")
except TokenExpiredError:
    # Clear persisted tokens and redirect user to login
    auth.revoke()
    return redirect("/login")
```

---

## 配置参考

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

#### 参数参考

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

### 暂存环境

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

```python
auth = ServerToServerAuth(
    client_id="...",
    client_secret="...",
    ims_base_url="https://ims-na1-stg1.adobelogin.com",
)
```

### 自定义 HTTP 客户端

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

```python
import httpx

http_client = httpx.Client(
    proxy="http://corporate-proxy:8080",
    verify="/path/to/custom-ca-bundle.pem",
)

auth = ServerToServerAuth(
    client_id="...",
    client_secret="...",
    http_client=http_client,
)
```

---

## 线程安全

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