Specify CLI 认证机制详解:auth.json 配置、GitHub / Azure DevOps 鉴权与凭证安全
2026/9/6 22:32:48 网站建设 项目流程

Specify CLI 认证机制详解:auth.json 配置、GitHub / Azure DevOps 鉴权与凭证安全

【免费下载链接】spec-kit💫 Toolkit to help you get started with Spec-Driven Development项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit

本文基于 spec-kit 仓库的认证参考文档,完整讲解 Specify CLI 的 opt-in(显式启用)认证机制:如何编写~/.specify/auth.jsonhosts/provider/auth等字段的取值与校验规则、GitHub 与 Azure DevOps 各类认证方案(bearer、basic-pat、azure-cli、azure-ad)的配置示例,以及凭证匹配、401/403 回退与重定向凭证剥离的底层实现。读完后你可以为公共 GitHub、GitHub Enterprise Server 或 Azure DevOps 私有目录(catalog)/扩展(extension)/预设(preset)下载正确配置凭据,并理解其安全边界。

1. 设计原则:不配置即不发送凭据

Specify CLI 对目录源 HTTP 请求、扩展下载和版本发布检查均采用**可选认证(opt-in authentication)**模型:

  • 只有当你显式创建~/.specify/auth.json时,CLI 才会向对应主机附加Authorization头;
  • 该文件不存在时,所有 HTTP 请求均以未认证方式发出;
  • 配置文件中“哪些主机 + 哪个 provider + 哪种认证方案”由你声明,provider 类定义“如何认证”(Bearer、Basic-PAT 等)。

这一模型在 src/specify_cli/authentication/init.py 的模块 docstring 中被明确陈述,其内置 provider 注册表AUTH_REGISTRY通过_register_builtins()注册了githubazure-devops两个内置 provider。

2. 配置文件结构

认证配置全部集中在用户主目录下的单个 JSON 文件中:

mkdir -p ~/.specify # 将下文 JSON 写入 ~/.specify/auth.json chmod 600 ~/.specify/auth.json

最小可用的 GitHub 配置:

{ "providers": [ { "hosts": ["github.com", "api.github.com", "raw.githubusercontent.com", "codeload.github.com"], "provider": "github", "auth": "bearer", "token_env": "GH_TOKEN" } ] }

安全提示:建议将文件权限收紧为仅属主可读写(chmod 600)。这一点并非只是文档建议——从源码看,config.py 在加载配置时会检查文件权限位,若在 POSIX 系统上该文件对 group/others 可读,会发出UserWarning提醒执行chmod 600,但不会因此失败。

2.1 字段参考

providers数组中每个条目的通用字段:

字段必填说明
hosts该条目适用的主机名数组。仅支持精确主机名或以*.开头的子域通配(如*.visualstudio.com)。*.visualstudio.com匹配foo.visualstudio.com,但不匹配visualstudio.com本身。*github.comgith?b.com等其他 glob 模式不被支持,会在加载时被拒绝。
provider内置 provider 键:githubazure-devops
auth认证方案,见下节。
token内联 token 值。可能时优先使用token_env
token_env存放 token 的环境变量名。

azure-ad方案额外要求三个字段:

字段必填说明
tenant_idAzure AD 租户 ID。
client_id服务主体(service principal)客户端 ID。
client_secret_env存放 client secret 的环境变量名。

bearerbasic-pat方案必须至少设置tokentoken_env之一。

2.2 源码中的校验规则

load_auth_config() 对配置的约束比文档表格更具体,了解它们可以避免配置不生效:

  • host 模式白名单_is_valid_host_pattern()只接受两种形式——精确主机名和*.suffix。它显式拒绝含?[]的模式,以及*出现在其他位置的写法。注释中说明动机:*github.com会匹配github.com.evil.com这类危险形式,因此必须禁用。
  • 归一化:所有hosts值在存储前会被strip().lower()token_envtenant_idclient_idclient_secret_env等字符串引用字段经_norm()去除首尾空白,防止“验证通过但环境变量查不到”的静默故障。
  • provider/scheme 兼容性:未知provider或该 provider 不支持的auth值会抛出ValueError,错误信息会列出已注册的 provider 或该 provider 支持的方案列表。
  • schema 违规的容错策略:文件不存在返回空列表(即全部未认证请求);JSON 结构错误则抛ValueError,而更高层的 HTTP 辅助函数会捕获它、告警后继续以未认证方式运行(见 http.py 的_load_config(),配置按进程缓存,文件最多读取一次)。

3. Provider 与认证方案

3.1 GitHub(github

方案请求头适用场景
bearerAuthorization: Bearer <token>PAT、细粒度 PAT(fine-grained PAT)、OAuth token、GitHub App token

GitHubAuth 类只声明了key = "github"supported_auth_schemes = ("bearer",),其auth_headers()对非bearer方案直接抛ValueError;token 解析则继承基类默认逻辑:优先读entry.token,否则读token_env指定的环境变量,并对值做strip()

示例 — 通过环境变量注入 PAT:

{ "hosts": ["github.com", "api.github.com", "raw.githubusercontent.com", "codeload.github.com"], "provider": "github", "auth": "bearer", "token_env": "GH_TOKEN" }

3.2 GitHub Enterprise Server(GHES)

若目录或扩展托管在自管的 GHES 实例上,只需添加一条github条目列出该实例的主机名。同一条目既用于认证 catalog JSON 拉取,也用于私有 release 资产下载——Specify 会识别这些主机为 GitHub Enterprise,并将 release 下载解析到 GHES REST API(/api/v3)。

{ "providers": [ { "hosts": ["ghes.example.com", "raw.ghes.example.com", "codeload.ghes.example.com"], "provider": "github", "auth": "bearer", "token_env": "GH_ENTERPRISE_TOKEN" } ] }

配置要点:

  • 必须列出裸 web 主机(如ghes.example.com),因为 release 下载 URL 就挂在它下面;
  • 若实例使用子域隔离,还要把 catalog/扩展 URL 实际用到的raw.codeload.子域一并列出;
  • *.ghes.example.com通配只匹配子域、不匹配裸主机,因此裸主机必须显式列出。

源码侧可以印证这一机制:http.py 的github_provider_hosts()会收集auth.json中所有githubprovider 条目的hosts,供 resolve_github_release_asset_api_url() 使用。该函数把浏览器式下载 URL(https://<host>/<owner>/<repo>/releases/download/<tag>/<asset>)解析为 REST API 资产 URL;主机是否按 GHES 处理正是由这份白名单决定——未列入的主机不会被当作 GHES,从而阻止恶意 catalog 诱导向任意主机发起 API 请求。扩展、预设、workflow 的安装路径(如 extensions/__init__.py、presets/__init__.py、commands/bundle/__init__.py)都通过传入github_provider_hosts()复用这一逻辑。

3.3 Azure DevOps(azure-devops

AzureDevOpsAuth 支持四种方案:

方案请求头适用场景
basic-patAuthorization: Basic base64(:<PAT>)个人访问令牌(PAT)
bearerAuthorization: Bearer <token>预先获取的 OAuth / Azure AD token
azure-cliAuthorization: Bearer <token>通过az account get-access-token获取 token
azure-adAuthorization: Bearer <token>通过 OAuth2 client credentials 流程获取 token

示例 — 环境变量注入 PAT:

{ "hosts": ["dev.azure.com"], "provider": "azure-devops", "auth": "basic-pat", "token_env": "AZURE_DEVOPS_PAT" }

示例 — Azure CLI(交互式登录):

{ "hosts": ["dev.azure.com"], "provider": "azure-devops", "auth": "azure-cli" }

要求此前已执行az login。从源码看,_acquire_via_az_cli()会以 30 秒超时执行az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --output json并解析accessToken字段;它特意用shutil.which(尊重 Windows 的PATHEXT)解析出绝对路径az,避免工作目录中一个名为az.cmd的恶意文件被当成凭证操作执行——任何失败都会返回None并回退到下一个策略,而不是抛出异常。

示例 — Azure AD 服务主体(CI/自动化):

{ "hosts": ["dev.azure.com"], "provider": "azure-devops", "auth": "azure-ad", "tenant_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "client_secret_env": "AZURE_CLIENT_SECRET" }

_acquire_via_client_credentials()会向https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token发起 POST,请求client_credentials授权与499b84ac-1321-427f-aa17-267ca6975798/.default作用域。该请求有三层防护:拒绝一切 307/308 重定向(否则 POST 体中的client_secret会被原样转发到别处);响应经read_response_limited按上限读取,超大响应视为失败;网络故障、JSON 解析失败等一律返回None落入回退。

4. 多条目配置

可以同时配置多个条目以覆盖不同主机或组织,条目按数组顺序匹配:

{ "providers": [ { "hosts": ["github.com", "api.github.com", "raw.githubusercontent.com", "codeload.github.com"], "provider": "github", "auth": "bearer", "token_env": "GH_TOKEN" }, { "hosts": ["dev.azure.com"], "provider": "azure-devops", "auth": "basic-pat", "token_env": "AZURE_DEVOPS_PAT" } ] }

5. 工作机制与源码级实现

文档描述的运行时行为可概括为五步,下面逐条对照 src/specify_cli/authentication/http.py 的实现:

  1. 主机匹配:每个出站请求先用 find_entries_for_url() 解析 URL 的 hostname,与auth.json中各条目的hosts模式比对(*.前缀通配对hostname.endswith(pattern[1:])求值,因此只匹配子域)。畸形 authority(如未闭合的 IPv6 方括号)被视为无主机名,返回空匹配而非抛异常。
  2. 附加凭证:命中后由对应 provider 的resolve_token()取 token,auth_headers()构造Authorization头。build_request()中有一个防绕过细节:extra_headers里的Authorization键会被剥除,认证头最后合并,外部无法覆盖。
  3. 401/403 回退:open_url() 遍历所有匹配条目,某一轮收到 401/403 时关闭响应、尝试下一条目;其他错误(404、500、网络错误)立即抛出。
  4. 未认证兜底:所有条目用尽(或无匹配)后,以无认证请求作为最后回退。
  5. 重定向凭证剥离:每次尝试都安装隔离的 opener 并挂上_StripAuthOnRedirect处理器。重定向时它会:校验目标必须为带主机名的 HTTPS(仅允许环回地址之间使用 HTTP);若新主机不在该条目的声明主机内、或发生 HTTPS→HTTP 降级,则从请求和unredirected_hdrs中同时移除Authorization,防止凭据泄漏到 CDN 或第三方服务;目标 URL 畸形则抛URLError交由上层下载错误处理。

隔离 opener 的动机是代码注释中说明的:open_url()每次自建 opener,使得进程中若有全局urllib.request.install_opener也无法替换掉重定向守卫。

6. 与其他鉴权路径的关系

仓库中另有一条独立的轻量路径:build_github_request() 直接读取GITHUB_TOKEN/GH_TOKEN环境变量,但只针对内置的四个 GitHub 官方域名(GITHUB_HOSTSgithub.comapi.github.comraw.githubusercontent.comcodeload.github.com)附加 Bearer 头,非 GitHub 主机一律不加,避免向第三方主机泄漏凭据。从源码结构看,配置驱动的open_url()/build_request()是扩展、预设、bundle、workflow 安装时下载资产的主通道(tests/test_authentication.pytests/http_helpers.py对其行为有测试覆盖),而build_github_request()服务于对 GitHub 官方域名的简单请求场景。

7. 快速上手模板

一份预置 GitHub 配置的参考auth.json

{ "providers": [ { "hosts": [ "github.com", "api.github.com", "raw.githubusercontent.com", "codeload.github.com" ], "provider": "github", "auth": "bearer", "token_env": "GH_TOKEN" } ] }

启用步骤:

mkdir -p ~/.specify # 将上方 JSON 复制到 ~/.specify/auth.json chmod 600 ~/.specify/auth.json # 使用前确保 GH_TOKEN 已导出

小结

spec-kit 的认证体系围绕单一事实源~/.specify/auth.json展开:以主机白名单控制凭据作用范围,以 provider/scheme 双层键约束认证方式,并在请求链路上实现了 401/403 逐条目回退、未认证兜底、重定向剥离与跨域防泄漏等安全行为。对使用公共 GitHub 的用户,最小配置即是一条bearer+token_env条目;对 GHES 用户,额外列出裸主机即可同时打通 catalog 拉取与私有 release 下载;对 Azure DevOps 用户,则可根据交互或 CI 场景在basic-patazure-cliazure-ad三种动态/静态方案中选择。

【免费下载链接】spec-kit💫 Toolkit to help you get started with Spec-Driven Development项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询