OAuth2 流程与凭证存储
OAuth2 流程与凭据存储
相关源文件
本章引用的主要源码文件:
crates/jcode-auth-types/Cargo.tomlcrates/jcode-auth-types/src/lib.rssrc/auth/claude.rssrc/auth/claude_tests.rssrc/auth/codex.rssrc/auth/codex_tests.rssrc/auth/copilot.rssrc/auth/copilot_auth_tests.rssrc/auth/cursor.rssrc/auth/cursor_tests.rssrc/auth/doctor.rssrc/auth/gemini.rssrc/auth/gemini_tests.rssrc/auth/google.rssrc/auth/login_diagnostics.rssrc/auth/oauth.rssrc/auth/oauth_tests/basic.rssrc/auth/oauth_tests/flow.rssrc/auth/refresh_state.rssrc/auth/status_types.rssrc/auth/tests.rssrc/auth/validation.rssrc/cli/commands/report_info.rssrc/storage.rssrc/tui/app/auth_account_commands.rssrc/tui/app/auth_account_picker.rssrc/tui/app/tests/commands_accounts_02/part_01.rssrc/usage.rs
本文档详细介绍了 jcode 中 OAuth2 认证生命周期的实现,包括授权码交换证明密钥(PKCE)流程、本地回调基础设施以及用户主目录中凭据的持久化存储。
基于 PKCE 的 OAuth2 生命周期
jcode 实现了 OAuth 2.0 PKCE 流程,用于安全地与 Anthropic(Claude)、OpenAI 和 Google(Gemini)等提供商进行认证,而无需在 CLI 发起的流程中使用客户端密钥。
实现细节
该流程主要在 src/auth/oauth.rs 中管理,涉及以下步骤:
- PKCE 生成:创建一个加密随机的
code_verifier,并使用 SHA-256 推导出code_challengesrc/auth/oauth.rs:97-114。 - 状态生成:生成一个随机十六进制字符串用于 CSRF 保护
src/auth/oauth.rs:117-120。 - 授权请求:CLI 构造一个指向提供商
/authorize端点(例如https://claude.com/cai/oauth/authorizesrc/auth/oauth.rs:17-19)的 URL,其中包含code_challenge和state。 - 令牌交换:收到授权码后,使用原始的
code_verifier在提供商的/token端点将其交换为access_token和refresh_token。
本地 TCP 回调监听器
为了捕获来自浏览器的重定向,jcode 会启动一个临时的本地 HTTP 服务器。
- 监听器:
TcpListener绑定到本地端口(例如 OpenAI 的端口 1455src/auth/oauth.rs:36)以接收授权码src/auth/oauth.rs:7。 - 请求处理:
read_http_request_line_blocking和drain_http_headers_blocking处理传入的 HTTP 请求src/auth/oauth.rs:151-173。 - 安全验证:监听器会验证
state参数是否与生成的 CSRF 状态匹配。如果无效,则返回bad_request_responsesrc/auth/oauth.rs:132-142。
手动 URL 回退
在无法访问本地服务器的环境中(例如没有端口转发的远程 SSH),jcode 提供了手动回退方案。如果无法打开浏览器或回调监听器失败,CLI 会提示用户手动访问 URL 并粘贴返回的回调代码 src/auth/google.rs:197-209。
OAuth 流程数据流
下图说明了 TUI、本地监听器和外部 OAuth 提供商之间的交互。
图表:OAuth2 PKCE 序列
来源:src/auth/oauth.rs:97-120、src/auth/oauth.rs:151-173、src/auth/google.rs:153-195、src/auth/claude.rs:228-230
凭据存储与安全
jcode 将凭据存储在用户主目录中的标准化位置,具体位于 ~/.jcode/ 下。
关键位置
| 提供商 | 文件路径 | 描述 |
|---|---|---|
| Anthropic/Claude | ~/.jcode/auth.json | 存储多账户 Claude OAuth 令牌 src/auth/claude.rs:181-183 |
| OpenAI | ~/.jcode/openai-auth.json | 存储 OpenAI OAuth 账户和活动选择 src/auth/codex.rs:113-115 |
| Gemini | ~/.jcode/gemini_oauth.json | 存储原生 Google Gemini OAuth 令牌 src/auth/gemini.rs:119-121 |
| Google/Gmail | ~/.jcode/google_oauth.json | 存储用于 Google/Gmail 通知集成的令牌 src/auth/google.rs:71-73 |
auth.json 格式
Anthropic/Claude 的存储使用多账户格式。
AnthropicAccount:包含label、access、refresh、expires和scopes的结构体src/auth/claude.rs:49-60。JcodeAuthFile:auth.json中的根结构,用于跟踪anthropic_accounts和active_anthropic_accountsrc/auth/claude.rs:64-73。它支持从旧版单账户布局进行迁移src/auth/claude.rs:199-215。
安全措施
- 文件权限:
jcode在读取或写入凭据文件时会调用harden_secret_file_permissions,以确保文件仅对所有者可读src/auth/claude.rs:194、src/auth/google.rs:77。 - 秘密写入:像
write_json_secret这样的工具函数可确保对敏感 JSON 数据进行原子写入并限制权限src/auth/google.rs:112、src/auth/codex.rs:192。
来源:src/auth/claude.rs:49-73、src/auth/claude.rs:181-183、src/auth/codex.rs:113-115、src/auth/google.rs:67-73、src/auth/google.rs:110-113
认证状态管理
认证状态通过一组枚举和一个缓存层进行管理,以平衡响应速度和准确性。
AuthState 枚举
AuthState 枚举(定义在 crates/jcode-auth-types/src/lib.rs 中)表示特定凭据的可用性:
Available:凭据存在且有效。Expired:配置存在,但令牌可能需要刷新。NotConfigured:未找到凭据。
AUTH_STATUS_CACHE
探测凭据可能代价较高(例如扫描 PATH 查找 cursor-agent 或读取 SQLite 数据库)。
- TTL:系统使用
AUTH_STATUS_CACHE来避免冗余的环境探测。 - 快速缓存:
AuthStatus::check_fast()提供高频探测,避免昂贵的子进程操作,如cursor-agent statussrc/auth/cursor.rs:102-104。 - 使用缓存:提供商使用报告会单独缓存 2 分钟(
PROVIDER_USAGE_CACHE_TTL),以避免触发提供商的速率限制src/usage.rs:43-50。
图表:认证状态解析逻辑
来源:src/usage.rs:43-50、src/auth/cursor.rs:102-104、src/cli/commands/report_info.rs:159-162
外部凭据发现
如果用户授权,jcode 可以从其他已安装的 AI 工具中“借用”凭据。
发现逻辑
- Claude Code:查找
~/.claude/.credentials.jsonsrc/auth/claude.rs:173-175。 - Copilot:扫描
~/.copilot/config.json、hosts.json和apps.jsonsrc/auth/copilot.rs:164-193。 - Cursor:探测 Cursor IDE 使用的 SQLite
state.vscdb,使用sqlite3CLI 提取cursorAuth/accessTokensrc/auth/cursor.rs:186-212。 - Gemini CLI:从
~/.gemini/oauth_creds.json导入src/auth/gemini.rs:123-125。
同意机制
为了维护安全,来自 Gemini CLI 或 Cursor 等来源的外部凭据被视为“未经同意”,直到用户通过 Config::allow_external_auth_source_for_path 明确允许它们 src/auth/gemini.rs:146-153、src/auth/cursor.rs:169-176。
来源:src/auth/claude.rs:173-175、src/auth/copilot.rs:164-193、src/auth/cursor.rs:186-212、src/auth/gemini.rs:146-153