news 2026/9/13 15:40:09

Backstage CLI Auth 模块实战:用 OAuth 2.0 + PKCE 实现 CLI 与 Backstage 实例的安全认证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Backstage CLI Auth 模块实战:用 OAuth 2.0 + PKCE 实现 CLI 与 Backstage 实例的安全认证

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 loginauth logoutauth showauth listauth print-tokenauth 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');

其中:

  • scopeopenid offline_accessopenid用于获取用户身份,offline_access用于获取 refresh token,保证访问令牌过期后可以静默续期;
  • state:32 字节随机数的 hex 编码(crypto.randomBytes(32).toString('hex')),用于防止 CSRF,回调时若 state 不匹配会直接抛出State mismatch
  • code_challenge_methodS256:与前面 SHA-256 的实现一一对应。

令牌交换与刷新

授权码拿到后,CLI 通过本地回调服务器换取令牌(grant_type=authorization_code,附上code_verifier),令牌响应格式在 auth.ts 中用 Zod schema 校验,包含access_tokentoken_typeexpires_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.yaml
  • app-config.*.yaml
  • packages/*/app-config.yaml
  • packages/*/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 元数据文件;
  • 复用已有实例时会保留其selectedmetadata字段,因此重新登录不会丢失默认实例标记。

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):

  1. 若指定了--instance则直接使用,否则交互式选择(pickInstance);
  2. 若有 refresh token,先向实例的/api/auth/v1/revoke端点发送token_type_hint=refresh_token的撤销请求——这是best-effort行为,遵循 RFC 7009,失败会被捕获忽略;
  3. 从密钥存储中删除 access token 与 refresh token;
  4. 从 YAML 元数据文件中移除该实例记录;
  5. 输出Logged out

示例

yarn backstage-cli auth logout --instance production

交互式登出:

yarn backstage-cli auth logout

auth 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 production

auth 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.com

auth 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 staging

auth select:切换默认实例

Usage: backstage-cli auth select [options] Select the default instance Options: --instance <name> Name of the instance to select

select决定其他 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、issuedAtaccessTokenExpiresAtselected标记以及可选的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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 15:39:53

注塑成型纤维取向模拟:从Jeffery方程到方向张量的工程实践

简介&#xff1a;面向不连续纤维复合材料流动诱导取向模拟的Matlab函数工具包&#xff0c;适合从事短纤维注塑成型、纤维取向演化及微观力学性能预测的研究与工程人员。代码实现Jeffrey、Folgar-Tucker、Phelps-Tucker等多类取向模型&#xff0c;提供四阶方向张量闭合近似、平面…

作者头像 李华
网站建设 2026/9/13 15:37:43

SpringBoot+Android养老院健康管理系统开发实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:36:30

网络安全新人基本功怎么练?五类典型练习题全拆解

经常有人问我&#xff0c;网络安全新人的基本功到底怎么练。我的回答一直很朴素&#xff1a;先做一套能打的练习题。别误会&#xff0c;我不是让你背题库&#xff0c;而是通过题目把密码学、访问控制、攻击防御、安全管理、实操排错这几条线全部串起来。“计算机网络安全与管理…

作者头像 李华
网站建设 2026/9/13 15:36:16

重庆各级公路Shapefile数据处理与应用:从文件结构到缓冲区分析

简介&#xff1a;面向GIS分析与交通规划人员&#xff0c;提供重庆全市各级公路网络与区县行政区划矢量数据。资源按一级公路、二级公路、三级公路及高速公路分类整理&#xff0c;同时包含重庆各区县边界shp&#xff0c;可用于路网可视化、区域连通性分析、交通可达性计算及城市…

作者头像 李华