集成与凭证 OAuth
Block 要调 Google/GitHub 等第三方服务需要凭证。Day 08 从 Block 视角看过,今天从平台管理视角:系统 vs 用户凭据、OAuth 授权流程、凭据管理器。
👶 小白:我用 Google 账号授权给平台之后,平台是不是就拿到我的 Google 密码了?
👨🏫 老师:没有,一次都拿不到你的密码。OAuth 的精妙就在这:你在 Google 自己的页面上登录、点"同意",Google 只发给平台一张有限权限的令牌(token)——像给你请的管家一张"只能进厨房、不能进卧室"的临时卡。平台拿这张 token 代表你调 API,卡能随时吊销,而你的密码始终只在 Google 手里。state 参数则防止别人伪造这趟授权(CSRF)。
两类凭据
系统凭据(平台托管)
- 平台自己出钱的一批 API key
- 写死固定 UUID(credentials_store.py:58)
- 如 openai/anthropic 的 key
- 用它 → 平台付费、按 credit 收你钱
用户凭据
- 用户自己接的账号(OAuth)或填的 key
- 如你自己的 GitHub 授权
- 加密存在用户的 UserIntegrations
- 用它 → 用你自己的额度
is_system_credential(id)(credentials_store.py:297)判断是哪类——这正是 Day 16 平台成本追踪的判据(用系统凭据才记平台成本)。系统凭据托管
# 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)
title 明确写"Use Credits"提示用户"这会消耗你的 credit"。凭据存取(加密)
IntegrationCredentialsStore(credentials_store.py:307)负责持久化——用户凭据加密存在 UserIntegrations。方法:add_creds、get_all_creds(会把配了环境变量的系统凭据也拼进来)、get_creds_by_id、update_creds。
SecretStr(内存里防误打印)配合——凭据全生命周期都被保护:内存里 SecretStr 包裹、存储时加密、传输时只传引用(Day 08)。多层防护是敏感数据的标准做法。OAuth 登录流程
用户连接第三方账号(如 GitHub)走 OAuth(api/features/integrations/router.py),BaseOAuthHandler(integrations/oauth/base.py:12)定义了两个抽象步骤 get_login_url(:26)与 exchange_code_for_tokens(:35):
GET /{provider}/login(:93):生成带 state token 的授权 URL,用户跳转过去授权。- 用户在第三方页面点"同意" → 第三方带 code 回调
POST /{provider}/callback(:182)。 - 验 state →
exchange_code_for_tokens用 code 换 access/refresh token → 存库。
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 显示是"你"发的。integrations/oauth/google.py/github.py/...,都继承 BaseOAuthHandler)。state 防 CSRF
OAuth login 生成一个 state token(credentials_store.py:530 的 store_state_token),callback 时 verify_state_token(:575)校验,还带 PKCE code challenge。
凭据管理器(Day 08 复习+平台视角)
IntegrationCredentialsManager(creds_manager.py:74)是执行时用的门面,职责:按需刷新 OAuth token + 分布式锁保证同一凭据同一时刻只被一个 Block 用(Day 08 讲过):
get()(:125):取凭据,OAuth2 快过期就refresh_if_needed()。_refresh_locked()(:207):加锁调oauth_handler.refresh_tokens()换新 token 存回。acquire()(:145):取凭据 + 拿系统级读写锁。
create_mcp_oauth_handler()(:347)从凭据 metadata 现场重建 handler。整套凭据管理(引用/实体分离 + 加密存储 + 自动刷新 + 加锁)保证了"Block 安全地用第三方服务"——这是可视化 Agent 平台能安全地接入几百个服务的基础。执行时注入回顾
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
acquire 取出解密、刷新、加锁的凭证注入 run(Day 05)→ Block 用它调 API →(用系统凭据则记平台成本,Day 16)。Day 05/08/16/17 讲的是同一条凭据/计费链的不同环节——现在你能看清全貌了。今日小结 + 动手
🧠 今天你应该能回答
- 系统凭据 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