认证优先级:分层裁决下的凭证决策逻辑
Claude Code 的认证机制并非简单的“先来后到”,也不是单一的账号源。它是一套高度结构化的解析链,决定了在当前的会话请求中,到底该把哪一套身份凭证(Credentials)注入到 API 客户端。
从定义开始
认证优先级本质上是 针对多种凭证来源的互斥裁决(Exclusive Precedence)。
在 Claude Code 中,同时存在三条并行的认证路径:
- 第三方提供商(3P Providers):如 AWS Bedrock、Google Vertex AI、Foundry。
- 第一方直连(Direct Connect):包括
ANTHROPIC_API_KEY、环境变量、以及用户自定义的apiKeyHelper脚本。 - Claude.ai 托管(Managed OAuth):即通过
/login获取的订阅态 OAuth Token。
它不是什么:它不是一个简单的“账号列表”。运行时会根据当前的环境上下文(如是否在 CI 中、是否开启了 --bare)来动态切断某些路径。
源码级拆解
这套裁决逻辑的核心入口在 claude-code-opensource/src/utils/auth.ts,而非 /login 命令。它遵循以下四个阶梯式的判断流:
1. 路径分流:OAuth 是否启用
isAnthropicAuthEnabled() 会首先执行逻辑排除:
- Provider 优先级:如果检测到
CLAUDE_CODE_USE_BEDROCK等第三方标志,第一方 OAuth 路径将立即退出竞争。 - 环境硬约束:使用
--bare启动时,OAuth 会被强制禁用,系统仅允许通过环境变量或apiKeyHelper提供凭证。
2. Bearer Token 来源排序
getAuthTokenSource() 负责查找有效的 Authorization 头:
- 优先环境变量:
ANTHROPIC_AUTH_TOKEN拥有极高的优先级,通常用于特定的安全网关场景。 - 降级到托管配置:如果环境变量缺失,系统才会去查找
/login留下的CLAUDE_CODE_OAUTH_TOKEN。 - 特别注意:在 Trust Dialog(信任对话框)确认之前,系统会由于安全原因拒绝执行
apiKeyHelper脚本,以防止未经授权的本地执行风险。
3. API Key 的精细化解析
getAnthropicApiKeyWithSource() 处理的是 X-Api-Key 路径:
- 批准记忆(Approval Persistence):在交互模式下,即使环境变量中存在
ANTHROPIC_API_KEY,也必须经过用户明确的“批准”对话框,结果会被记录在customApiKeyResponses.approved中。 - Helper 缓存语义:
apiKeyHelper被视为显式的“主来源”。一旦配置,系统会优先尝试从 Helper 获取 key。Helper 具备 Stale-while-revalidate 语义,默认有 5 分钟的 TTL 缓存(受CLAUDE_CODE_API_KEY_HELPER_TTL_MS控制)。
4. 托管 OAuth 上下文(Managed Context)
这是一个极高的特殊层级。对于 Claude Desktop 或 远程桥接(Remote Bridge) 会话,系统会识别为 isManagedOAuthContext()。在这种场景下,为了确保企业级管控的一致性,本地的环境变量或个人 API Key 不会被允许“反客为主”地篡改托管会话的认证方式。
踩坑指南
/login的局限性:如果你发现/login后依然无法使用某些高级功能,请检查你的环境变量。如果ANTHROPIC_API_KEY存在且已生效,它会“压住”/login的托管权限。- Provider 的独立性:一旦进入 AWS Bedrock 或 Google Vertex 路径,所有的 OAuth 刷新、Claude.ai 订阅状态判断都将不再参与,因为系统已经切换到了完全不同的 SDK 底座。
- 信任边界(Trust Boundary):存储在项目级(Local Settings)的
apiKeyHelper受 Workspace Trust 限制。这意味着在你通过/add-dir或信任该目录前,该 Helper 不会被执行。 - 缓存的一致性:
apiKeyHelper具有缓存。如果你更新了外部密钥管理器,可能需要等待缓存过期或通过claude auth logout强制刷新。
延伸阅读
- 如果你想了解如何处理认证过程中的 Token 刷新(Refresh),请看
claude-code-opensource/src/services/api/client.ts。 - 如果你关心不同模式(Default vs Bare)对环境感知的区别,请看 Bare 模式:极致的上下文隔离与执行纯净度。
- 如果你想知道如何排查当前的认证状态,请看 权限管理与规则配置:/permissions 详解。
源码锚点
- claude-code-opensource/src/utils/auth.ts — 核心:包含
isAnthropicAuthEnabled、getAuthTokenSource、getAnthropicApiKeyWithSource等所有裁决函数的聚合点。
📄 src/utils/auth.ts — 核心:包含 `isAnthropicAuthEnabled`、`getAuthTokenSource`、`getAnthropicApiKeyWithSource` 等所有裁决函数的聚合点。L100-129 of 2003
typescript
export function isAnthropicAuthEnabled(): boolean {
// --bare: API-key-only, never OAuth.
if (isBareMode()) return false
// `claude ssh` remote: ANTHROPIC_UNIX_SOCKET tunnels API calls through a
// local auth-injecting proxy. The launcher sets CLAUDE_CODE_OAUTH_TOKEN as a
// placeholder iff the local side is a subscriber (so the remote includes the
// oauth-2025 beta header to match what the proxy will inject). The remote's
// ~/.claude settings (apiKeyHelper, settings.env.ANTHROPIC_API_KEY) MUST NOT
// flip this — they'd cause a header mismatch with the proxy and a bogus
// "invalid x-api-key" from the API. See src/ssh/sshAuthProxy.ts.
if (process.env.ANTHROPIC_UNIX_SOCKET) {
return !!process.env.CLAUDE_CODE_OAUTH_TOKEN
}
const is3P =
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY)
// Check if user has configured an external API key source
// This allows externally-provided API keys to work (without requiring proxy configuration)
const settings = getSettings_DEPRECATED() || {}
const apiKeyHelper = settings.apiKeyHelper
const hasExternalAuthToken =
process.env.ANTHROPIC_AUTH_TOKEN ||
apiKeyHelper ||
process.env.CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR
// Check if API key is from an external source (not managed by /login)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- claude-code-opensource/src/services/api/client.ts — 实现:请求客户端根据 Provider 分流的具体逻辑。
📄 src/services/api/client.ts — 实现:请求客户端根据 Provider 分流的具体逻辑。L16-19 of 390
typescript
getAPIProvider,
isFirstPartyAnthropicBaseUrl,
} from 'src/utils/model/providers.js'
import { getProxyFetchOptions } from 'src/utils/proxy.js'1
2
3
4
2
3
4
- claude-code-opensource/src/hooks/useApiKeyVerification.ts — 交互:UI 线程如何预热
apiKeyHelper缓存并执行验证。
📄 src/hooks/useApiKeyVerification.ts — 交互:UI 线程如何预热 `apiKeyHelper` 缓存并执行验证。L29-33 of 85
typescript
// Use skipRetrievingKeyFromApiKeyHelper to avoid executing apiKeyHelper
// before trust dialog is shown (security: prevents RCE via settings.json)
const { key, source } = getAnthropicApiKeyWithSource({
skipRetrievingKeyFromApiKeyHelper: true,
})1
2
3
4
5
2
3
4
5
- claude-code-opensource/src/cli/handlers/auth.ts — 显示:
claude auth status如何翻译底层认证流状态。
📄 src/cli/handlers/auth.ts — 显示:`claude auth status` 如何翻译底层认证流状态。L8-11 of 331
typescript
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../../services/analytics/index.js'
import { getSSLErrorHint } from '../../services/api/errorUtils.js'1
2
3
4
2
3
4