Day 17 / 共 20 天 · 第 4 周 平台底座

集成与凭证 OAuth

Block 要调 Google/GitHub 等第三方服务需要凭证。Day 08 从 Block 视角看过,今天从平台管理视角:系统 vs 用户凭据、OAuth 授权流程、凭据管理器。

📍 你在整门课的位置 · 第 4 周 平台化与收官
D16 Credits 计费 D17 OAuth D18 REST API D19 经典/Forge
💡 今天的类比世界观:凭据与 OAuth = "门禁卡 + 委托授权" 从平台管理视角看凭据:系统凭据 = 公司统一发的门禁卡(平台托管,大家用平台的额度);用户凭据 = 你自己的私人卡OAuth 登录 = 你授权平台"代表你"进 Google/GitHub 办事,但不把家门钥匙(密码)给它;state 防 CSRF = 一张防伪回执单,验明这趟授权确实是你发起的;加密存储 = 卡锁进保险柜。今天都用"门禁 / 委托"来想。

👶 小白:我用 Google 账号授权给平台之后,平台是不是就拿到我的 Google 密码了?

👨‍🏫 老师:没有,一次都拿不到你的密码。OAuth 的精妙就在这:你在 Google 自己的页面上登录、点"同意",Google 只发给平台一张有限权限的令牌(token)——像给你请的管家一张"只能进厨房、不能进卧室"的临时卡。平台拿这张 token 代表你调 API,卡能随时吊销,而你的密码始终只在 Google 手里。state 参数则防止别人伪造这趟授权(CSRF)。

L01

两类凭据

系统凭据(平台托管)

  • 平台自己出钱的一批 API key
  • 写死固定 UUID(credentials_store.py:58)
  • 如 openai/anthropic 的 key
  • 用它 → 平台付费、按 credit 收你钱

用户凭据

  • 用户自己接的账号(OAuth)或填的 key
  • 如你自己的 GitHub 授权
  • 加密存在用户的 UserIntegrations
  • 用它 → 用你自己的额度
为什么要区分两类? 系统凭据:让新手零配置就能用——不用自己去 OpenAI 申请 key,直接用平台的,按 credit 付费(Day 16)。用户凭据:让高级用户用自己的账号/额度——比如用你自己的 GitHub 账号发 PR(PR 显示是你发的)、用你自己的 OpenAI key(不走平台计费)。系统凭据降低上手门槛,用户凭据提供灵活性——两者兼顾。is_system_credential(id)credentials_store.py:297)判断是哪类——这正是 Day 16 平台成本追踪的判据(用系统凭据才记平台成本)。
L02

系统凭据托管

# integrations/credentials_store.py:58 —— 写死固定 UUID
openai_credentials = APIKeyCredentials(
    id="53c25cb8-...",          # 固定 UUID
    provider="openai",
    api_key=SecretStr(settings.secrets.openai_api_key),  # 平台的 key(从 env)
    title="Use Credits for OpenAI", expires_at=None,
)
# 汇总成 DEFAULT_CREDENTIALS(:259),派生 SYSTEM_CREDENTIAL_IDS(:291)
读法:系统凭据用固定 UUID + 平台自己的 API key(从环境变量读)。用户在画布上选"Use Credits for OpenAI"就是用这个——真实 key 由平台提供,用户只按 credit 付费。title 明确写"Use Credits"提示用户"这会消耗你的 credit"。
L03

凭据存取(加密)

IntegrationCredentialsStorecredentials_store.py:307)负责持久化——用户凭据加密存UserIntegrations。方法:add_credsget_all_creds(会把配了环境变量的系统凭据也拼进来)、get_creds_by_idupdate_creds

凭据必须加密存储 用户的 API key、OAuth token 是高度敏感的——泄漏了别人就能冒充你调服务、花你的钱。存数据库前必须加密(即使数据库被拖库,拿到的也是密文)。这和 Day 08 的 SecretStr(内存里防误打印)配合——凭据全生命周期都被保护:内存里 SecretStr 包裹、存储时加密、传输时只传引用(Day 08)。多层防护是敏感数据的标准做法。
L04

OAuth 登录流程

🤔 痛点:想让 AutoGPT 替你发 GitHub PR,总不能把 GitHub 密码给它吧? 你希望 Agent 能"以你的身份"操作 GitHub。最"直接"的办法是把账号密码填给它——但这太危险了:AutoGPT(或任何存了你密码的系统)一旦泄漏,别人就拿到了你 GitHub 的全部权限、且无法单独撤销。有没有办法"授权它做有限的事,又不交出密码"?
💡 本质:OAuth = 用一张"有限、可撤销的令牌"代替密码 OAuth 的核心是把"证明你是谁"和"允许别人代表你做某事"分开。你不给密码,而是跳到 GitHub 亲自点"同意",GitHub 再发给 AutoGPT 一个 access token(权限受限、可随时在 GitHub 后台撤销、还能定期刷新)。密码始终只在你和 GitHub 之间,第三方永远拿不到。这就是"授权而不交出身份"。

用户连接第三方账号(如 GitHub)走 OAuth(api/features/integrations/router.py),BaseOAuthHandlerintegrations/oauth/base.py:12)定义了两个抽象步骤 get_login_url:26)与 exchange_code_for_tokens:35):

OAuth 三步:拿授权 URL → 用户同意回 code → 用 code 换 token 你(用户) AutoGPTOAuth handler GitHub ①给你授权 URL ②你跳过去点"同意",GitHub 带 code 回调 ③用 code 换 token
全程你的密码只在你↔GitHub 之间;AutoGPT 拿到的只是一张有限、可撤销的 token。每个第三方一个 handler,都继承 BaseOAuthHandler。
  1. GET /{provider}/login:93):生成带 state token 的授权 URL,用户跳转过去授权。
  2. 用户在第三方页面点"同意" → 第三方带 code 回调 POST /{provider}/callback:182)。
  3. 验 state → exchange_code_for_tokens 用 code 换 access/refresh token → 存库。
📝 举个例子:连接 GitHub 时地址栏发生了什么 ① 点"连接 GitHub",跳到 github.com/login/oauth/authorize?client_id=...&state=x9f2..&scope=repo
② 你在 GitHub 点"Authorize",浏览器被带回 .../github/callback?code=abc123&state=x9f2..
③ 后端核对 state=x9f2.. 与发起时一致 → 拿 code=abc123 去 GitHub 换回 {access_token:"gho_...", refresh_token:"..."} → 加密存进你的 UserIntegrations之后 Block 发 PR,就用这张 token,PR 显示是"你"发的。
OAuth 是什么?为什么不直接要密码? OAuth 是"授权而不给密码"的标准协议。你要让 AutoGPT 代表你操作 GitHub,不是把 GitHub 密码告诉 AutoGPT(危险!),而是:跳转到 GitHub → GitHub 问你"同意 AutoGPT 访问吗" → 你同意 → GitHub 给 AutoGPT 一个令牌(token)。这个令牌权限有限、可随时撤销、不等于密码。OAuth 让第三方应用"有限地代表你行动",而你的密码从不外泄——这是现代授权的基石。每个第三方一个 handler(integrations/oauth/google.py/github.py/...,都继承 BaseOAuthHandler)。
L05

state 防 CSRF

OAuth login 生成一个 state token(credentials_store.py:530store_state_token),callback 时 verify_state_token:575)校验,还带 PKCE code challenge。

state 防的是什么攻击? CSRF(跨站请求伪造)——攻击者可能伪造一个 OAuth 回调,把他的账号 token 塞给你的会话(让你的 Agent 用了攻击者的账号,或反之)。state 是登录时生成的随机值,回调时校验"这个回调确实对应我发起的那次登录"——伪造的回调没有正确的 state,被拒。PKCE 是进一步的防护(防授权码被截获)。OAuth 流程里 state 校验是安全必备——少了它就有账号劫持风险。安全无小事,尤其涉及第三方授权。
L06

凭据管理器(Day 08 复习+平台视角)

IntegrationCredentialsManagercreds_manager.py:74)是执行时用的门面,职责:按需刷新 OAuth token + 分布式锁保证同一凭据同一时刻只被一个 Block 用(Day 08 讲过):

  • get():125):取凭据,OAuth2 快过期就 refresh_if_needed()
  • _refresh_locked():207):加锁调 oauth_handler.refresh_tokens() 换新 token 存回。
  • acquire():145):取凭据 + 拿系统级读写锁。
MCP 是特例:endpoint 动态发现,用 create_mcp_oauth_handler():347)从凭据 metadata 现场重建 handler。整套凭据管理(引用/实体分离 + 加密存储 + 自动刷新 + 加锁)保证了"Block 安全地用第三方服务"——这是可视化 Agent 平台能安全地接入几百个服务的基础。
L07

执行时注入回顾

Day 05/08 讲过的执行时注入(executor/manager.py:263),串起整条凭据链:

credentials, lock = await creds_manager.acquire(user_id, credentials_meta.id)
extra_exec_kwargs[field_name] = credentials
# ... block.execute(input_data, **extra_exec_kwargs)  # 注入到 run
完整凭据链(三天串起来):Block 声明凭据字段(Day 08)→ 用户连接账号走 OAuth 存加密凭据(今天 L04)或用系统凭据(L02)→ 执行时 acquire 取出解密、刷新、加锁的凭证注入 run(Day 05)→ Block 用它调 API →(用系统凭据则记平台成本,Day 16)。Day 05/08/16/17 讲的是同一条凭据/计费链的不同环节——现在你能看清全貌了。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 系统凭据 vs 用户凭据各解决什么?
  • 凭据为什么要加密存储 + SecretStr + 只传引用(多层防护)?
  • OAuth 为什么"授权而不给密码"?
  • state token 防什么攻击?
  • 凭据管理器的职责?完整凭据链怎么串?

✋ 动手

P=autogpt_platform/backend/backend/integrations
grep -n 'openai_credentials\|SYSTEM_CREDENTIAL_IDS\|is_system_credential' $P/credentials_store.py
grep -n 'def get\|def acquire\|def refresh_if_needed' $P/creds_manager.py
grep -n 'login\|callback\|store_state_token' $P/../api/features/integrations/router.py | head
明天预告 · Day 18API / REST 层——FastAPI 应用结构、进程编排、主要端点(建图/执行/商店)、付费墙 402、Store 市场。看平台对外的门面。
← Day 16 Credits Day 18 · API/REST →