Backstage CLI Auth 模块实战:用 OAuth 2.0 + PKCE 实现 CLI 与 Backstage 实例的安全认证
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
@backstage/cli-module-auth是 Backstage 官方 CLI 的认证模块,为backstage-cli提供与 Backstage 实例之间的登录、登出与凭证管理能力。本文将以 docs/tooling/cli/module-auth.md 为主线,结合仓库中 packages/cli-module-auth 的完整源码,系统讲解auth login、auth logout、auth show、auth list、auth print-token、auth select六个命令的用法、底层 OAuth 2.0 PKCE 授权流程,以及凭证的本地存储机制。读完本文,你将掌握如何让 CLI 安全地访问多个 Backstage 实例,并在脚本与 CI 流水线中复用访问令牌。
模块概览:认证能力的承载者
@backstage/cli-module-auth是一个 CLI 模块(CLI Module),通过createCliModule注册到 Backstage CLI 的命令体系中。从 模块入口 可以看到,它一次性注册了 6 个auth子命令:
| 命令 | 说明 |
|---|---|
auth login | 登录 CLI 到某个 Backstage 实例 |
auth logout | 退出登录并清除本地凭证 |
auth show | 显示某个已认证实例的详情 |
auth list | 列出所有已认证实例 |
auth print-token | 向 stdout 输出访问令牌(必要时自动刷新) |
auth select | 选择默认实例 |
这些命令获取到的访问令牌会被其他 CLI 命令复用,例如 actions 命令 在调用 Backstage 后端 API 时使用认证模块产出的令牌。
认证原理:OAuth 2.0 Authorization Code + PKCE
该模块采用OAuth 2.0 Authorization Code + PKCE(Proof Key for Code Exchange)流程获取访问令牌,而不是简单的用户名密码。之所以选择 PKCE,是因为 CLI 本质上是一个"公开客户端"(无法安全保管 client secret),PKCE 通过动态生成的 code verifier 与 code challenge 防止授权码被截获后重放。
PKCE 的核心实现
在 pkce.ts 中可以看到两个关键函数:
generateVerifier(length = 64):使用crypto.randomBytes生成随机字节并做 base64url 编码,得到 code verifier(长度限制在 43~128 字符内);challengeFromVerifier(verifier):对 verifier 做 SHA-256 哈希后 base64url 编码,得到 code challenge,即S256变换方法。
export function generateVerifier(length = 64): string { const bytes = crypto.randomBytes(Math.max(32, Math.min(96, length))); return base64url(bytes); } export function challengeFromVerifier(verifier: string): string { const hash = crypto.createHash('sha256').update(verifier).digest(); return base64url(hash); }授权请求参数
在 login.ts 的buildAuthorizeUrl中可以看到完整的授权请求参数:
authorize.searchParams.set('client_id', clientId); authorize.searchParams.set('redirect_uri', redirectUri); authorize.searchParams.set('response_type', 'code'); authorize.searchParams.set('scope', 'openid offline_access'); authorize.searchParams.set('state', state); authorize.searchParams.set('code_challenge', challenge); authorize.searchParams.set('code_challenge_method', 'S256');其中:
scope为openid offline_access:openid用于获取用户身份,offline_access用于获取 refresh token,保证访问令牌过期后可以静默续期;state:32 字节随机数的 hex 编码(crypto.randomBytes(32).toString('hex')),用于防止 CSRF,回调时若 state 不匹配会直接抛出State mismatch;code_challenge_method为S256:与前面 SHA-256 的实现一一对应。
令牌交换与刷新
授权码拿到后,CLI 通过本地回调服务器换取令牌(grant_type=authorization_code,附上code_verifier),令牌响应格式在 auth.ts 中用 Zod schema 校验,包含access_token、token_type、expires_in和可选的refresh_token。
访问令牌的刷新逻辑同样在auth.ts:
accessTokenNeedsRefresh:当令牌距离过期时间不足2 分钟(Date.now() + 2 * 60_000)时视为需要刷新;refreshAccessToken:向实例的/api/auth/v1/token端点以grant_type=refresh_token发起 POST 请求,成功后将新的 access token(以及可能轮换的 refresh token)写回系统密钥存储,并更新过期时间戳;- 若 refresh token 不存在,会抛出
Access token is expired and no refresh token is available。
刷新过程在withMetadataLock中执行,避免多进程并发写坏元数据文件。
前置条件:启用 CLI 认证
要使用本模块,目标 Backstage 实例必须开启 CLI 认证支持。CLI 的检测方式是请求实例的 well-known 端点:
/api/auth/.well-known/oauth-client/cli.json在 login.ts 中,clientId被设置为该端点 URL,登录时先fetch它,如果返回非 2xx,会抛出:
Server does not support CLI authentication. Ensure CIMD is enabled on the backend.也就是说,后端需要启用 CIMD(Client-Initiated Mutual Device / CLI 认证相关能力)才能配合本模块工作。部署 Backstage 时若需使用 CLI 认证,请确保后端的 auth 插件支持该 well-known 端点。
实例名(Instance Names)机制
每个已认证的 Backstage 实例都存放在一个你自己命名的短标签下,其他命令通过--instance <name>引用它,例如--instance production。
命名规则:
- 若登录时未指定名称,CLI 会从后端 URL 的 hostname 派生,如
https://backstage.example.com派生为backstage.example.com(见login.ts中的deriveInstanceName); - 实例名必须匹配正则
^[a-zA-Z0-9._:@-]+$,否则元数据写入会被 Zod schema 拒绝(见 storage.ts)。
auth login:登录 CLI 到 Backstage 实例
启动 OAuth 授权流程:打开浏览器完成认证,随后将凭证保存到本地。
Usage: backstage-cli auth login [options] Log in the CLI to a Backstage instance Options: --backendUrl <url> Backend base URL --noBrowser Do not open browser automatically --instance <name> A short name for this instance, used to refer to it in other auth commands. Defaults to the backend URL hostname.交互式登录的 URL 发现逻辑
不带任何参数运行时,命令是交互式的(pickBaseUrl函数)。它会扫描当前目录下的这些文件来发现后端地址:
app-config.yamlapp-config.*.yamlpackages/*/app-config.yamlpackages/*/app-config.*.yaml
读取其中的backend.baseUrl作为候选,让你选择或手动输入。若已有已认证实例,还会先询问是复用已有实例还是新增实例(promptForInstance)。实例名由 URL 的 host 自动派生。
浏览器打开方式
openInBrowser根据平台调用不同命令:macOS 用open,Windows 用powershell Start-Process,Linux 用xdg-open。源码注释特别说明不使用react-dev-utils/openBrowser,因为它会二次编码 URL 参数导致登录链接损坏。
示例
登录(交互式):
yarn backstage-cli auth login登录指定后端 URL:
yarn backstage-cli auth login --backendUrl https://backstage.example.com登录并命名实例,便于后续引用:
yarn backstage-cli auth login --backendUrl https://backstage.example.com --instance production不自动打开浏览器(授权 URL 会打印到终端,手动打开):
yarn backstage-cli auth login --backendUrl https://backstage.example.com --noBrowser登录成功后的持久化
登录成功后(persistInstance):
- access token 与 refresh token 写入系统密钥存储;
- 若服务器未返回 refresh token,会向 stderr 打印警告:"You will need to re-authenticate when the access token expires";
- 实例元数据(名称、baseUrl、clientId、issuedAt、accessTokenExpiresAt 等)写入 YAML 元数据文件;
- 复用已有实例时会保留其
selected与metadata字段,因此重新登录不会丢失默认实例标记。
auth logout:退出登录并清除凭证
Usage: backstage-cli auth logout [options] Log out the CLI and clear stored credentials Options: --instance <name> Name of the instance to log out登出流程(见 logout.ts):
- 若指定了
--instance则直接使用,否则交互式选择(pickInstance); - 若有 refresh token,先向实例的
/api/auth/v1/revoke端点发送token_type_hint=refresh_token的撤销请求——这是best-effort行为,遵循 RFC 7009,失败会被捕获忽略; - 从密钥存储中删除 access token 与 refresh token;
- 从 YAML 元数据文件中移除该实例记录;
- 输出
Logged out。
示例
yarn backstage-cli auth logout --instance production交互式登出:
yarn backstage-cli auth logoutauth show:查看实例与当前用户详情
Usage: backstage-cli auth show [options] Show details of an authenticated instance Options: --instance <name> Name of the instance to show该命令(show.ts)通过CliAuth.create({ instanceName })获取访问令牌(过期会自动刷新),然后请求实例的/api/auth/v1/userinfo端点,输出当前用户身份(claims.sub)与 ownership 实体引用(claims.ent):
User: user:default/example-user Ownership: - group:default/team-a - group:default/team-b示例
查看默认实例:
yarn backstage-cli auth show查看指定实例:
yarn backstage-cli auth show --instance productionauth list:列出已认证实例
Usage: backstage-cli auth list List authenticated instances默认实例以星号(*)标记(见 list.ts 中inst.name === selected?.name ? '* ' : ' ')。若没有任何实例,向 stderr 输出No instances found。
示例:
yarn backstage-cli auth list输出示例:
* production - https://backstage.example.com staging - https://backstage-staging.example.comauth print-token:为脚本与流水线输出访问令牌
Usage: backstage-cli auth print-token [options] Print an access token to stdout (auto-refresh if needed) Options: --instance <name> Name of the instance to use实现上(printToken.ts)只做三件事:创建CliAuth上下文、调用auth.getAccessToken()(内部会依据"过期前 2 分钟"规则自动刷新)、把令牌打印到 stdout。正因为令牌过期会自动刷新,该命令非常适合写入脚本与 CI 流水线。
示例——打印默认实例的令牌:
yarn backstage-cli auth print-token示例——结合 curl 调用 Backstage API:
curl -H "Authorization: Bearer $(yarn backstage-cli auth print-token)" \ https://backstage.example.com/api/catalog/entities示例——指定命名实例:
yarn backstage-cli auth print-token --instance stagingauth select:切换默认实例
Usage: backstage-cli auth select [options] Select the default instance Options: --instance <name> Name of the instance to selectselect决定其他 auth 命令在未传--instance时使用哪个实例。内部调用setSelectedInstance(storage.ts):将目标实例的selected置为true,其余实例置为false;若名称不存在则抛出Unknown instance '<name>'。切换成功后向 stderr 输出Selected instance '<name>'。
不传--instance时交互式选择。
示例
yarn backstage-cli auth select --instance production交互式选择:
yarn backstage-cli auth select凭证存储:元数据与令牌分离
认证状态分两处存储(详见 storage.ts):
实例元数据(YAML 文件)
文件路径为~/.config/backstage-cli/auth-instances.yaml(Linux/macOS),具体由getMetadataFilePath决定:
- 优先使用环境变量
XDG_CONFIG_HOME; - Windows 下使用
%APPDATA%(即AppData/Roaming); - 否则使用
~/.config。
该 YAML 文件保存实例名、后端 URL、clientId、issuedAt、accessTokenExpiresAt、selected标记以及可选的metadata扩展字段。写入时使用mode: 0o600(仅当前用户可读写),并通过proper-lockfile加锁(withMetadataLock,最多重试 5 次)保证并发安全。元数据读取会经过 Zod schema 校验,解析失败时按空列表处理。
令牌(系统密钥存储)
access token 与 refresh token 存放在系统 secret store 中(通过getSecretStore()获取),与 YAML 元数据文件分离。以"服务 + 键"的形式管理(secretStore.set(service, 'accessToken' | 'refreshToken', token)),避免把敏感令牌明文写进 YAML 文件。
这种"元数据 + 密钥"分离的设计,让实例清单可读、可审计,同时令牌本身受到系统级密钥存储保护,是 CLI 凭证管理的最佳实践。
小结
@backstage/cli-module-auth用标准的 OAuth 2.0 Authorization Code + PKCE(S256)为 Backstage CLI 提供了完整、可脚本化的认证能力:login负责授权登录,logout负责撤销并清理,show查看身份与 ownership,list管理多实例视图,print-token让脚本与 CI 无缝复用自动刷新的令牌,select控制默认实例。实例元数据存于auth-instances.yaml(0600 权限 + 文件锁),令牌存于系统密钥存储,两者分离保证了安全性与可维护性。如果你在开发自己的 Backstage CLI 插件或需要编写与后端交互的自动化工具,这套认证链路(源码、登录实现、存储实现)本身就是一份可直接参考的实现蓝本。
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考