Backstage 新认证服务迁移指南:从旧版 identity/tokenManager 到 auth/httpAuth
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
本文基于 Backstage 仓库中的 auth-service-migration.md 编写,系统讲解自 1.24 版本起重构后的新认证(auth)服务。文章覆盖两部分内容:一是后端(Backend)实例层面如何应对默认认证策略(default auth policy)带来的破坏性变更;二是插件与模块(Plugin & Module)层面如何从旧的identity/tokenManager服务迁移到新的auth/httpAuth服务,并给出了可直接套用的代码示例与配置片段。读完本文,你将掌握新认证架构的核心概念、本地开发的 guest 登录配置,以及三个最常见的服务调用迁移范式。
背景:1.24 起认证服务重构
Backstage 后端系统(new backend system)的认证服务在 1.24 版本中进行了重新设计。除了一组新服务之外,最核心的变更在于:新后端系统中运行的所有插件默认都会拦截所有未经用户或服务认证的请求,这一行为被称为默认认证策略(default auth policy)。
这是本次更新引入的唯一具有破坏性的生产变更,它同时影响:
- 现有后端实例的部署与本地开发;
- 所有插件的请求处理行为(未迁移的插件可能出现原本允许匿名访问的端点被默认策略拦截的情况)。
新认证服务的引入还取代了此前 contrib 目录下的 authenticate-api-requests.md 指南(该文件位于仓库的 contrib 目录中)。如果你的后端此前安装了该指南所描述的实现,应当移除并改用新认证服务。
一、后端实例迁移
使用新认证服务的前提是后端已经运行在新后端系统之上。如果仍在使用旧系统,需要先按照迁移到新后端系统的指南完成升级。
1.1 选择:保留默认认证策略 or 关闭它
升级到最新版本时,你可以选择两条路径:
| 路径 | 行为 | 适用场景 |
|---|---|---|
| 保留默认认证策略 | 所有请求必须携带用户或服务凭据,否则被拦截 | 生产环境推荐,安全性最佳 |
| 关闭默认认证策略 | 允许无凭据请求进入插件 | 暂未完成迁移的过渡期(未来版本将移除该选项) |
如果你选择保留默认策略,需要确保请求(包括本地开发时的请求)都经过认证;如果你此前安装了 contrib 中的 authenticate-api-requests 实现但暂时不想移除,也可以先关闭默认策略再平滑过渡。
1.2 关闭默认认证策略(过渡方案)
在 app-config 中设置以下配置即可关闭默认认证策略:
backend: auth: dangerouslyDisableDefaultAuthPolicy: true注意:该功能将在未来版本中移除。关闭后,请求即使不带任何凭据也能进入后端插件,但请求仍会被视为"未认证",并非所有插件端点都能接受这种请求。若想了解该配置的完整影响,可阅读auth 服务文档中的"Configuring the service"一节——文档同时强调:如果启用了权限系统(permissions),未认证请求会被原样交给权限策略决定允许哪些权限,这一点同样适用于插件之间的服务调用(除非你为服务调用配置了凭据)。
务必不要在生产环境关闭默认认证策略,除非确实必要,并尽快迁移到新认证服务,否则你需要自行维护签发 token 的服务。
1.3 保留默认策略:本地开发启用 guest 登录
保留默认策略后,本地开发也需要认证。若你此前依赖'guest'访客身份进行本地开发,推荐安装新的 guest provider 模块:
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-guest-provider然后将其注册到后端入口文件packages/backend/src/index.ts:
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));最后在开发配置中加入 guest provider:
auth: providers: guest: {}guest provider 只应服务于本地开发,不要在生产使用。它默认会拒绝在生产环境启用,但最好完全避免。如果你没有独立的开发配置文件,请在生产配置中显式禁用:
auth: providers: guest: null如果确实需要在非开发环境启用 guest 登录,可以这样配置(有安全风险,谨慎使用):
auth: providers: guest: dangerouslyAllowOutsideDevelopment: true完成上述三步后,guest 认证即可工作:@backstage/core-components中的默认SignInPage会自动检测并启用 guest provider。
从源码结构看,该模块的实现位于 plugins/auth-backend-module-guest-provider/src/module.ts:它通过createBackendModule注册auth插件的guest-provider模块,使用createProxyAuthProviderFactory组合guestAuthenticator与signInAsGuestUser解析器,并从auth.providers.guest配置读取设置(例如dangerouslyAllowOutsideDevelopment),由此可确认文档中"guest provider 会读取该配置节"的行为确实由signInAsGuestUser(config.getConfig('auth.providers.guest'))支撑。
1.4 自定义 identity / tokenManager 的适配
由于默认认证策略对运行在新后端系统中的所有插件统一生效,你不必逐个检查插件是否受保护。尚未迁移的插件可能暴露的隐患是:某些本应允许匿名访问的端点现在会被默认策略拦截。
- 若想为某个插件临时放行,可以为该插件安装一个模块,通过 http router 服务 添加所需策略(具体写法见下文"插件与模块迁移"部分)。
- 如果你有自定义的 identity 或 token manager 服务实现,可以使用
@backstage/backend-common中的createLegacyAuthAdapters辅助函数将它们适配到新认证服务上(该函数同样用于插件内部的兼容层,详见下文)。
二、插件与模块迁移
插件/模块的迁移分为两个步骤,优先级不同:
| 步骤 | 内容 | 紧迫程度 |
|---|---|---|
| 第一步 | 为新后端系统添加所需的认证策略(auth policy) | 更紧急,否则插件可能在新后端系统中无法正常工作 |
| 第二步 | 迁移到新的 auth 服务 | 不太紧急,直到旧认证服务被移除前都非必须 |
2.1 添加认证策略(Auth Policy)
如果插件支持新后端系统,且需要接受未认证请求或仅凭用户 cookie 认证的请求,就必须通过httpRouter服务添加例外策略。例如,允许/health端点接受未认证请求:
export default createBackendPlugin({ pluginId: 'example', register(env) { env.registerInit({ deps: { config: coreServices.rootConfig, logger: coreServices.logger, httpRouter: coreServices.httpRouter, auth: coreServices.auth, httpAuth: coreServices.httpAuth, }, async init({ config, logger, httpRouter, auth, httpAuth }) { httpRouter.use(await createRouter({ config, logger, auth, httpAuth })); // 新增:为 /health 端点添加未认证放行策略 httpRouter.addAuthPolicy({ path: '/health', allow: 'unauthenticated', }); }, }); }, });addAuthPolicy是httpRouter服务暴露的 API(定义于 packages/backend-plugin-api/src/services/definitions/HttpRouterService.ts),它允许你按路径声明允许的认证级别;框架会在你的后端代码执行前,依据这些策略完成对入站 token 的预先校验。完整的策略类型与用法可参考 http router 服务文档。
2.2 使用新 auth 服务
本步骤的目标是:彻底移除插件内部对旧identity与tokenManager服务的使用,改用新的 auth 与 http auth 服务。
两点说明:
- 插件仍然可以把
identity/tokenManager作为可选依赖保留在插件环境中,以免破坏现有用户的配置; - 如果你的插件本就不依赖这两个服务,也不在内部使用
DefaultIdentityClient,则本步骤无需执行。
下文假设插件以createRouter模式作为对外 API(旧后端系统的典型写法)。如果你有其它外部 API 面,处理方式相同,只需相应调整示例。
2.2.1 更新新后端系统中的依赖声明
第一步:在createBackendPlugin的依赖中,把identity/tokenManager替换为auth/httpAuth。注意discovery服务必须保留或新增——它是后续兼容层所必需的:
export default createBackendPlugin({ pluginId: 'example', register(env) { env.registerInit({ deps: { config: coreServices.rootConfig, logger: coreServices.logger, discovery: coreServices.discovery, httpRouter: coreServices.httpRouter, // 移除 // identity: coreServices.identity, // tokenManager: coreServices.tokenManager, // 新增 auth: coreServices.auth, httpAuth: coreServices.httpAuth, }, async init({ config, logger, discovery, httpRouter, // auth, // httpAuth, }) { const router = await createRouter({ config, logger, discovery, auth, httpAuth, }); httpRouter.use(); }, }); }, });如果插件此前不依赖identity/tokenManager,直接忽略即可;但如果此前不依赖discovery,则必须把它加为必需依赖。
2.2.2 在createRouter中暴露新 auth 服务
为了让新 auth 服务以向后兼容的方式进入插件实现,使用@backstage/backend-common的createLegacyAuthAdapters辅助函数。它的行为是:
- 若提供了新服务实现(新后端系统的场景),直接透传;
- 若未提供新服务,则基于旧服务创建回退实现;旧服务也没有时,再回退到旧服务的默认实现。
实际改造createRouter的写法:
export interface RouterOptions { config: RootConfigService; logger: LoggerService; discovery: DiscoveryService; identity?: IdentityService; // 新增可选参数 auth?: AuthService; httpAuth?: HttpAuthService; } export function createRouter(options: RouterOptions) { // 关键:构造 auth / httpAuth const { auth, httpAuth } = createLegacyAuthAdapters(options); // ... 其余实现 }两条约束必须遵守:
- 如果
createRouter原本不接收identity/tokenManager参数,不要为了适配而新增它们; - 如果插件为这两个服务提供了任何默认实现,必须将其传给
createLegacyAuthAdapters。
这两条约束共同保证插件行为与迁移前完全一致。如果最终实现只需要auth或httpAuth其中之一,记得把用不到的那个从 RouterOptions 中移除。
2.2.3 替换旧认证服务调用
auth/httpAuth就绪后,剩下就是逐处替换identity/tokenManager的调用。以下是三个最常见的迁移范式。
范式一:发起独立的服务到服务请求
旧写法(获取一个服务 token):
const { token } = await tokenManager.getToken();新写法:
const { token } = await auth.getPluginRequestToken({ onBehalfOf: await auth.getOwnServiceCredentials(), targetPluginId: '<plugin-id>', // 例如 'catalog' });onBehalfOf指定"以谁的名义"发起请求:这里用的是插件自身凭据(getOwnServiceCredentials),适合插件作为调用发起者的场景(如周期性批处理索引任务);targetPluginId是新引入的必填项,它实现了对服务到服务认证更细粒度的控制:生成 token 时必须指明请求目标是哪个插件。
新方案要求"随取随用,不可复用":永远不要在请求前预先存储并复用 token,而应在真正发起请求的瞬间调用getPluginRequestToken,否则过期 token 会引发权限问题(该注意点同样记录在 auth 服务文档 中)。
范式二:转发来自入站请求的凭据
旧写法——直接取出原始 token 并透传给上游:
router.get('/example/:entityRef', async (req, _res) => { const token = getBearerTokenFromAuthorizationHeader( req.header('authorization'), ); // 用 token 调用下游,例如 catalog client const entity = await catalogClient.getEntityByRef(req.params.entityRef, { token, }); // 或者把 token 转发给权限评估 await permissions.authorize( [{ permission: examplePermission, resourceRef: entityRef }], { token }, ); });新写法——新服务刻意增加了一步:先从入站请求提取凭据,再基于凭据为上游请求生成新 token,从而避免用户 token 与服务 token 在链路中被直接透传:
router.get('/example/:entityRef', async (req, _res) => { const credentials = await httpAuth.credentials(req); // catalog client 目前只接受 token(未来会支持直接传凭据), // 因此这里需要基于凭据签发一个新 token const { token } = await auth.getPluginRequestToken({ onBehalfOf: credentials, targetPluginId: 'catalog', }); const entity = await catalogClient.getEntityByRef(req.params.entityRef, { token, }); // permissions 服务可以直接接收凭据 await permissions.authorize( [{ permission: examplePermission, resourceRef: entityRef }], { credentials }, ); });注意:要让上面permissions的调用成立,插件需要改为依赖@backstage/backend-plugin-api中的PermissionsService,而不是PermissionEvaluator。
通用原则:重构插件时应尽量让BackstageCredentials对象在内部尽可能远地传递,仅在真正使用 token 前一刻才生成 token。httpAuth.credentials用于从请求中提取已验证的凭据,其第二参数可选,用于限定接受的凭据类型(默认同时接受 service 与 user,但不含 limited access)。更多细节见 http auth 服务文档——该文档同时强调:不要仅仅为了"确认入站 token 有效"而调用httpAuth.credentials,框架会在你的代码执行前完成 token 有效性校验,只有当你确实需要基于凭据采取动作时才调用它。
范式三:从请求中获取用户身份
旧写法——通过identity服务:
router.get('/example/by-user', async (req, _res) => { const user = await identity.getIdentity({ request: req }); if (!user) { throw new AuthenticationError(); } console.log(`User ${user.identity.userEntityRef} is making a request`); });新写法:
router.get('/example/by-user', async (req, _res) => { const credentials = await httpAuth.credentials(req, { allow: ['user'] }); console.log( `User ${credentials.principal.userEntityRef} is making a request`, ); });allow: ['user']用于把可接受的凭据收窄为用户凭据;如果入站请求不是用户认证,credentials调用会抛出错误;- 如果业务逻辑并不强制要求用户已认证(仅在有用户时使用),可以传
allow: ['user', 'service', 'none'],然后检查credentials.principal.type再决定如何处理; auth服务还提供了auth.isPrincipal(credentials, 'user')这类辅助方法,用于在拿到凭据后进一步判断调用方类型并窄化 TypeScript 类型(详见 auth 服务文档)。
三、迁移检查清单
按以下顺序完成迁移,可最大限度降低破坏性:
- 确认后端运行在新后端系统(否则先迁移后端系统本身);
- 升级所有插件到最新版本,以包含对新 auth 服务的适配;
- 决定默认认证策略的去留:
- 保留 → 为本地开发配置 guest provider(仅开发环境);
- 关闭 → 设置
backend.auth.dangerouslyDisableDefaultAuthPolicy: true并尽快规划移除;
- 检查内部插件/模块:
- 需要放行匿名/cookie 请求的端点 → 用
httpRouter.addAuthPolicy添加策略; - 使用
identity/tokenManager的地方 → 用createLegacyAuthAdapters引入auth/httpAuth,并依次替换三类调用(独立服务调用、凭据转发、用户身份提取);
- 需要放行匿名/cookie 请求的端点 → 用
- 验证:本地开发用 guest 登录,确认受保护端点行为符合预期;对放行端点(如
/health)验证匿名访问可用。
延伸阅读
- Auth 服务文档:token 的生成、校验与凭据检查
- Http Auth 服务文档:Express 请求/响应上的凭据收发
- Http Router 服务文档:路由注册与认证策略控制
- 服务到服务认证:HTTP 请求链路中 token 的正确使用方式
- 新后端系统总览 与迁移指南
- Guest provider 模块源码:plugins/auth-backend-module-guest-provider/src/module.ts
httpRouter服务接口定义:packages/backend-plugin-api/src/services/definitions/HttpRouterService.ts
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考