ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Backstage 后端插件开发实战:从 `yarn new` 创建到服务注入与安全加固

Backstage 后端插件开发实战:从 `yarn new` 创建到服务注入与安全加固 Backstage 后端插件开发实战从yarn new创建到服务注入与安全加固【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文以 Backstage 新后端系统New Backend System为主线完整讲解后端插件backend plugin的创建、独立开发调试、接入应用、安全默认策略secure by default、依赖注入deps / init、数据库访问与用户身份获取的完整流程。读者在读完本文后将能够从零创建一个可独立运行、可挂载到 Backstage 主后端、并具备认证控制与持久化能力的生产级后端插件。本文主体基于 docs/plugins/backend-plugin.md并结合仓库内yarn new的实际脚手架模板packages/cli-module-new/templates/backend-plugin/与核心服务定义源码如 packages/backend-plugin-api/src/services/definitions/HttpRouterService.ts进行源码级印证与扩充。注意该页面属于 legacy plugins 文档的一部分虽然页面本身已按新后端系统模式书写但 Backstage 官方当前推荐的后端插件权威指南位于 docs/backend-system/building-plugins-and-modules/01-index.md本文内容与其相互印证、可对照阅读。创建后端插件在 Backstage 仓库根目录执行yarn new在交互式提示中选择backend-plugin即可生成一个最小可用的后端插件包yarn new创建过程会要求为插件提供一个名称。这个名称将成为 NPM 包名的一部分因此建议使用简短、只含小写字母和连字符的标识符。例如如果你要开发一个与名为 Carmen 的系统做集成的插件可以命名为carmen结合传给new命令的其他标志以及根目录package.json中针对new命令的配置最终 NPM 包名形如internal/plugin-carmen-backend。创建插件会花费一些时间因为脚手架会自动运行依赖安装与构建命令使包直接处于可开发状态。生成后的代码位于仓库plugins目录下的新文件夹中本例为plugins/carmen-backend。从仓库模板 packages/cli-module-new/templates/backend-plugin/package.json.hbs 可以看出一个全新的后端插件包具备以下特征backstage: { role: backend-plugin, pluginId: ... }声明该包是后端插件并标注插件 ID提供start/build/lint/test/clean/prepack/postpack等脚本统一基于backstage-cli package *命令默认依赖backstage/backend-defaults、backstage/backend-plugin-api、backstage/catalog-client、backstage/errors、express、express-promise-router、zod等测试相关依赖包括backstage/backend-test-utils、supertest。独立开发模式Standalone为方便开发后端插件可以被独立启动无需依赖整个 Backstage 应用。进入插件目录并启动cd plugins/carmen-backend yarn start启动一段时间后会输出Listening on :7007。在另一个终端窗口中执行curl localhost:7007/api/carmen/todos预期响应为{ items: [] }当前模板的默认路由就是/api/pluginId/todos如果希望为健康检查提供/health端点需要自己在 router 中实现。独立开发环境的实现在模板的 packages/cli-module-new/templates/backend-plugin/dev/index.ts.hbs 中。可以看到它用createBackend()组装了一个最小后端import { createBackend } from backstage/backend-defaults; import { mockServices } from backstage/backend-test-utils; import { catalogServiceMock } from backstage/plugin-catalog-node/testUtils; const backend createBackend(); // 使用 mock 的 auth 与 httpAuth 服务这样无需真实认证即可调用插件 API // 如需真实认证可改为 // backend.add(import(backstage/plugin-auth-backend)); // backend.add(import(backstage/plugin-auth-backend-module-guest-provider)); backend.add(mockServices.auth.factory()); backend.add(mockServices.httpAuth.factory()); // 使用带固定实体集合的 catalog mock而非真实 catalog backend.add( catalogServiceMock.factory({ entities: [ { apiVersion: backstage.io/v1alpha1, kind: Component, metadata: { name: sample, title: Sample Component }, spec: { type: service }, }, ], }), ); backend.add(import(../src)); backend.start();模板自带的 dev 注释还提供了几组可直接试用的 curl 命令可用于验证创建、列出 TODO 以及显式携带 mock 认证头的场景# 创建一条 TODO独立创建或关联 sample 组件 curl http://localhost:7007/api/pluginId/todos -H Content-Type: application/json -d {title: My Todo} curl http://localhost:7007/api/pluginId/todos -H Content-Type: application/json -d {title: My Todo, entityRef: component:default/sample} # 列出 TODO curl http://localhost:7007/api/pluginId/todos # 显式使用未认证 / 服务认证的 token curl http://localhost:7007/api/pluginId/todos -H Authorization: Bearer mock-none-token curl http://localhost:7007/api/pluginId/todos -H Authorization: Bearer mock-service-token将插件接入主后端一个新创建的后端插件在应用层面“什么都不做”它只有一组基础依赖并在src/service/router.ts中暴露一个 Express router。你需要把路由接入具体业务功能同时在 Backstage 应用/后端中显式挂载它。首先在 Backstage 根目录将插件包加入后端依赖包名以插件实际package.json中的为准yarn --cwd packages/backend add internal/plugin-carmen-backend^0.1.0然后修改packages/backend/src/indexconst backend createBackend(); // ... backend.add(import(internal/plugin-carmen-backend)); // ... backend.start();从仓库根目录用yarn start-backend启动后端后即可访问插件接口# 注意这里的 /api 前缀 curl localhost:7007/api/carmen/health预期返回{status:ok}说明插件已成功挂载到主后端。需要指出的是模板中插件的入口文件 packages/cli-module-new/templates/backend-plugin/src/index.ts.hbs 只是把plugin.ts中的插件对象以默认导出再导出一次export { {{pluginVar}} as default } from ./plugin;这正是backend.add(import(...))所要求的默认导出形态也是整个新后端系统“零配置装配”的基础。Secure by Default默认安全与认证策略自 Backstage 1.25 起插件体系开始转向 secure by default 模型插件收到的网络请求默认不允许未认证用户访问。以文档中允许未认证访问的/health请求为例其路由定义在plugins/carmen-backend/src/service/router.tsexport async function createRouter( options: RouterOptions, ): Promiseexpress.Router { // ... router.get(/health, (_, response) { logger.info(PONG!); response.json({ status: ok }); }); // ... return router; }可以看到路由本身没有定义任何认证机制只有路由名与响应数据——认证由插件定义plugins/carmen-backend/src/plugin.ts统一处理httpRouter.use( await createRouter({ logger, }), ); httpRouter.addAuthPolicy({ path: /health, allow: unauthenticated, });addAuthPolicy声明了对插件/health端点的放行策略允许该路径的请求以未认证身份通过而其他未显式声明的路径则继续遵循 secure by default 的默认拒绝行为。该 API 的底层契约定义在 packages/backend-plugin-api/src/services/definitions/HttpRouterService.tsaddAuthPolicy(policy: HttpRouterServiceAuthPolicy): void其中HttpRouterServiceAuthPolicy携带path与allow如unauthenticated字段而 HttpAuthService 文档中同样指出对未认证请求的放行应配合HttpRouterService.addAuthPolicy使用。从源码结构看实际的请求凭据校验由后端默认实现中的createCredentialsBarrier等机制承载参见 packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts 及同目录的 createCredentialsBarrier.ts它会拦截未携带有效凭据的请求只有命中显式放行策略的路径才能以未认证身份通过。使用依赖deps 声明与 init 注入新后端系统中依赖在注册阶段以静态方式声明在初始化阶段被“注入”。以插件定义 packages/cli-module-new/templates/backend-plugin/src/plugin.ts.hbs 为参照export const carmenPlugin createBackendPlugin({ pluginId: carmen, register(env) { env.registerInit({ deps: { httpRouter: coreServices.httpRouter, logger: coreServices.logger, }, async init({ httpRouter, logger }) { // ... }, }); }, });deps中的每一项都会在init的参数对象中可用。要添加自定义依赖只需在deps中新增具名条目deps: { myDependency: coreServices.rootConfig, },然后在init中通过解构访问async init({ myDependency }) { // .. }之后就可以自由地调用它或将其传入 router 等业务代码中。新后端系统的一个关键特性是plugin 作用域的服务如logger、httpRouter是“专属于当前插件”的实例——logger 可能会用插件 ID 标记日志httpRouter 可能会为 API 路由自动加上插件 ID 前缀具体行为取决于实现这让多插件并行运行时代码更加隔离与整洁。Backstage 内置了丰富的coreServices完整列表见 docs/backend-system/core-services/01-index.md。使用数据库Backstage 后端内置了统一的 SQL 数据库访问能力。大多数有持久化需求的插件都应优先使用该设施以便 Backstage 运维人员能够统一管理数据库需求。使用方式是在插件定义中依赖coreServices.database它会提供一个 Knex 连接对象deps: { // ... database: coreServices.database, }, async init({ database, }) { // 将 client 传入插件实际实现代码例如 const model new CarmenDatabaseModel(database); httpRouter.use( await createRouter({ model, logger, }), ); }所有插件数据库需求都配置在app-config.yaml的backend.database配置键下。框架还会在后台根据 Backstage 运维者设定的规则在逻辑数据库不存在时自动创建它。需要注意两点限制框架不负责数据库 schema 迁移主仓库中的内置插件选择使用 Knex 库来管理 schema 迁移你也可以采用任何你认为合适的方式迁移表命名冲突模块若与目标插件共享同一逻辑数据库实例应谨慎选择表名。01-index.md中给出的建议命名模式是package name__table name例如 scheduler 核心服务创建的backstage_backend_tasks__table表。若使用 Knex 默认迁移设施还应为其内部记账用的迁移状态表指定带前缀的名字避免与插件主迁移表冲突await knex.migrate.latest({ directory: migrationsDir, tableName: backstage_backend_tasks__knex_migrations, });Knex 迁移编写与 SQL 查询的具体示例参见 Knex 官方文档。使用用户身份Backstage 后端提供coreServices.httpAuth与coreServices.userInfo两个核心服务来访问用户身份。先在插件定义中声明deps: { httpAuth: coreServices.httpAuth, userInfo: coreServices.userInfo, }, async init({ httpAuth, userInfo, }) { httpRouter.use( await createRouter({ httpAuth, userInfo, logger, }), ); }随后在 router 中从请求里提取身份信息export interface RouterOptions { logger: LoggerService; userInfo: UserInfoService; httpAuth: HttpAuthService; } export async function createRouter( options: RouterOptions, ): Promiseexpress.Router { const { userInfo, httpAuth } options; router.post(/me, async (req, res) { const credentials await httpAuth.credentials(req, { // 这会拒绝来自非用户的请求。仅当插件确实需要访问用户身份时才使用 // 大多数情况下只需调用 httpAuth.credentials(req) 即可。 allow: [user], }); const user await userInfo.getUserInfo(credentials); res.json({ // 用户的 catalog 实体引用。 userEntityRef: user.userEntityRef, // 该用户或其所属团队所拥有的实体引用列表。 ownershipEntityRefs: user.ownershipEntityRefs, }); }); // ... }这一模式同样出现在模板 routerpackages/cli-module-new/templates/backend-plugin/src/router.ts中创建 TODO 的接口通过httpAuth.credentials(req, { allow: [user] })校验用户身份并把凭据传给业务服务。模板中的TodoListServicepackages/cli-module-new/templates/backend-plugin/src/services/TodoListService.ts进一步展示了两个进阶实践跨插件调用 catalog通过catalogServiceRef依赖 catalog 服务用getEntityByRef根据实体引用拉取实体跨插件通信使用服务到服务认证AuthService可生成仅对目标插件有效的 token若想以插件后端自身身份发起请求可调用auth.getOwnServiceCredentials()但要注意这绕过了用户权限检查记录创建者从options.credentials.principal.userEntityRef中读取当前用户实体引用写入 TODO 记录。源码视角模板中的完整数据流综合模板各文件可以看出一个后端插件的完整分层结构入口层src/index.ts.hbs 默认导出插件对象装配层src/plugin.ts.hbs 声明deps、在init中把服务注入 router路由层src/router.ts 使用express-promise-router定义 REST 路由用zod做请求体校验并用httpAuth.credentials把关身份业务层src/services/TodoListService.ts 通过createServiceRef/createServiceFactory定义可注入的服务测试层src/router.test.ts 直接针对createRouter做单元测试——用mockServices.httpAuth()模拟认证、用jest.Mocked模拟 todoList 服务并断言携带 mock 用户凭据的 POST 返回 201携带mockCredentials.none.header()未认证的 POST 返回 401。这个测试用例恰好从另一个角度印证了 secure by default 行为。测试依赖统一来自backstage/backend-test-utils如mockServices、mockCredentials、mockErrorHandler相关类型可参阅 packages/backend-test-utils 目录。下一步后端插件的权威指南与模块module开发、扩展点extension point机制见 docs/backend-system/building-plugins-and-modules/01-index.md核心服务的完整清单与用法见 docs/backend-system/core-services/01-index.md为新插件定义配置 schema 时参考 docs/conf/defining.md 中关于插件自定义配置的说明生成脚手架本身的实现位于 packages/cli-module-newyarn new即由该 CLI 模块驱动模板目录为 packages/cli-module-new/templates。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进