ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Backstage backend-defaults 认证 API 深度解读:authServiceFactory 与外部令牌处理器的完整认证链路

Backstage backend-defaults 认证 API 深度解读:authServiceFactory 与外部令牌处理器的完整认证链路 Backstage backend-defaults 认证 API 深度解读authServiceFactory 与外部令牌处理器的完整认证链路【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文以 report-auth.api.md 这份由 API Extractor 生成的backstage/backend-defaults认证 API 报告为主体结合packages/backend-defaults下的实际实现源码完整拆解 Backstage 新后端系统中认证服务的装配方式authServiceFactory如何组装默认的认证服务、ExternalTokenHandler接口如何被用于验证外部调用方的令牌以及PluginTokenHandler装饰器机制的扩展点设计。读完后你可以掌握如何在自定义后端插件中注册外部令牌处理器、如何在app-config中配置externalAccess与访问限制、以及插件间令牌签发的底层调用链。API 报告解读六个公开导出report-auth.api.md是 API Extractor 对backstage/backend-defaults包auth入口生成的 API 报告文件它精确锁定了该入口对外承诺的公开 API 面文件头部声明该文件不可手工编辑。报告共列出 6 个public导出导出类型职责authServiceFactoryServiceFactoryAuthService, plugin, singleton每个插件一个单例的默认认证服务工厂createExternalTokenHandler泛型函数以类型安全方式创建外部令牌处理器ExternalTokenHandler接口外部令牌处理器的契约type/initialize/verifyTokenexternalTokenHandlersServiceRefServiceRef..., plugin, multiton多实例注册表用于向 auth 服务注入自定义外部令牌处理器PluginTokenHandler接口插件间令牌的签发与校验契约pluginTokenHandlerDecoratorServiceRefServiceRef..., plugin, singleton对默认插件令牌处理器的装饰器扩展点这些符号的实际定义可以从 auth 入口导出文件 找到它分别从./authServiceFactory、./external/helpers、./external/ExternalAuthTokenHandler、./external/types和./plugin/PluginTokenHandler重新导出与报告一一对应。authServiceFactory默认认证服务的装配逻辑authServiceFactory.ts 中的authServiceFactory是整个默认认证服务的装配中心它声明了对 7 个依赖服务的需求第 61-71 行export const authServiceFactory createServiceFactory({ service: coreServices.auth, deps: { config: coreServices.rootConfig, logger: coreServices.logger, discovery: coreServices.discovery, plugin: coreServices.pluginMetadata, database: coreServices.database, pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef, externalTokenHandlers: externalTokenHandlersServiceRef, }, // ... });从工厂函数体第 72-125 行可以看清默认AuthService即内部类DefaultAuthService的组成要素读取危险开关backend.auth.dangerouslyDisableDefaultAuthPolicy第 81-84 行缺省为false。创建插件密钥来源createPluginKeySource({ config, database, logger, keyDuration })其中keyDuration固定为 1 小时第 86 行。该来源按配置决定使用数据库存储还是静态密钥对详见下文“插件间密钥来源”。用户令牌处理器UserTokenHandler.create({ discovery, logger })负责验证来自前端的用户令牌。插件令牌处理器先用pluginTokenHandlerDecorator(...)包裹一个DefaultPluginTokenHandler第 100-108 行装饰器机制见后文。外部令牌处理器ExternalAuthTokenHandler.create({ ownPluginId, config, logger, externalTokenHandlers })第 110-115 行聚合配置驱动的内置处理器与插件注入的自定义处理器。这三类令牌处理器正是DefaultAuthService.authenticate中令牌验证的三个分支。默认认证策略的验证顺序DefaultAuthService.ts 的authenticate方法第 64-118 行体现了“插件令牌优先、用户令牌次之、外部令牌兜底”的验证顺序插件令牌先交由pluginTokenHandler.verifyToken(token)验证。若命中且令牌携带limitedUserTokenon-behalf-of 委托场景再验证该受限用户令牌最终构造带用户主体user principal且附带受限令牌的凭证否则构造服务主体service principal凭证。用户令牌交由userTokenHandler.verifyToken(token)验证。注意第 93-98 行如果令牌是受限用户令牌limited user token而调用方未传allowLimitedAccess: true会直接抛出Illegal limited user token认证错误——受限令牌只能用于插件间委托不能被当作普通用户身份使用。外部令牌交由externalTokenHandler.verifyToken(token)验证成功后构造带accessRestrictions/allAccessRestrictions的服务主体凭证第 107-115 行。三者皆失败则抛出AuthenticationError(Illegal token)第 117 行。getPluginRequestToken插件间请求令牌的签发getPluginRequestToken第 151-219 行是插件调用其他插件时的核心方法按调用方主体类型分三种处理路径none主体 已禁用默认策略返回空令牌透传“未认证”状态第 164-166 行service主体若调用方自身携带访问限制外部令牌经accessRestrictions得到则校验目标插件是否在限制范围内且带权限名/权限属性的受限令牌不可再向下委托第 175-190 行会抛NotAllowedError校验通过后调用pluginTokenHandler.issueTokenuser主体先由userTokenHandler.createLimitedUserToken将用户令牌降级为受限令牌再以onBehalfOf: { limitedUserToken, expiresAt }的形式签发插件令牌第 197-213 行——这正是 API 报告中PluginTokenHandler.issueToken参数里onBehalfOf.limitedUserToken字段的来源。ExternalTokenHandler自定义外部令牌处理器接口契约external/types.ts 定义了处理器接口与 API 报告中的签名完全一致export interface ExternalTokenHandlerTContext { type: string; // 必须全局唯一对应配置中的 type 字段 initialize(ctx: { options: Config }): TContext; // 从配置构造上下文 verifyToken(token: string, ctx: TContext): Promise{ subject: string } | undefined; }type是配置路由的键initialize在启动时执行一次把该处理器的options配置段解析为类型化上下文TContextverifyToken每次请求调用返回{ subject }表示验证成功返回undefined表示“此令牌不归我管交给下一个处理器”。createExternalTokenHandlerexternal/helpers.ts 第 179-183 行本身是一个恒等函数其价值在于编译期类型安全与文档锚点——它保证了initialize返回的上下文类型与verifyToken第二参数类型一致。源码 JSDoc 中给出的示例写法是const customHandler createExternalTokenHandler({ type: custom, initialize({ options }) { return { apiKey: options.getString(apiKey) }; }, async verifyToken(token, context) { // 自定义验证逻辑 return { subject: custom:user }; }, });注册与配置加载链路自定义处理器通过externalTokenHandlersServiceRef注入。该 ref 定义于 ExternalAuthTokenHandler.ts 第 38-43 行id 为core.auth.externalTokenHandlers且标记为multiton: true——即每个插件可注册多个自定义处理器。ExternalAuthTokenHandler.create第 64-141 行完成了内置与自定义处理器的合并内置处理器有三个static、legacy、jwks第 45-49 行自定义处理器按handler.type索引类型重复会直接抛错第 77-90 行Duplicate external token handler type ... each handler must have a unique type随后遍历backend.auth.externalAccess配置数组第 92 行起对每个条目按type查找处理器未命中则抛出Unknown type ... expected one of static, legacy, jwks...错误命中则立即调用handler.initialize({ options })生成上下文并解析该条目的accessRestrictions。此外还保留了对旧配置键backend.auth.keys的兼容读取第 119-138 行若存在旧配置会以legacy处理器加载并打印一次性弃用警告指向 service-to-service 认证文档。内置处理器实现细节static静态令牌external/static.ts 实现最简单的调用方认证配置一个固定令牌与其 subject。initialize阶段的校验规则很严格第 27-39 行token必须是纯非空白字符且长度至少 8 位MIN_TOKEN_LENGTH 8subject同样不允许空白字符。verifyToken用严格相等比较匹配则返回配置的 subject。官方推荐的令牌生成方式是见 service-to-service-auth.mdnode -p require(crypto).randomBytes(24).toString(base64)对应的app-config配置形态backend: auth: externalAccess: - type: static options: token: ${CICD_TOKEN} subject: cicd-system-completion-events # 可选见下文“访问限制” accessRestrictions: - plugin: eventsjwks基于 JWKS 的 JWT 验证external/jwks.ts 面向使用 Auth0 等第三方 ID 签发的外部调用方。initialize第 37-60 行从options中解析选项必填说明url是JWKS 端点 URL必须为非空白字符串algorithm否允许的 JWT 算法字符串或数组issuer否允许的签发者字符串或数组audience否允许的受众字符串或数组subjectPrefix否subject 前缀验证阶段使用jose库的createRemoteJWKSetjwtVerify第 62-70 行并校验上述 algorithm/issuer/audience成功后 subject 会被强制加前缀有subjectPrefix时为external:prefix:sub否则为external:sub第 72-78 行——这个前缀约定保证了外部主体不会与内部插件主体命名空间冲突。legacy旧配置兼容legacy处理器对应已被弃用的backend.auth.keys配置段external/legacy.ts其语义与static相同仅由旧配置驱动加载。访问限制accessRestrictions的解析规则无论哪个处理器其配置条目下都可以挂accessRestrictions由 helpers.ts 的 readAccessRestrictionsFromConfig第 26-62 行解析为MappluginId, { permissionNames?, permissionAttributes? }。解析时的硬约束每个限制对象只允许plugin、permission、permissionAttribute三个键出现其他键直接抛错第 34-43 行同一插件不允许声明两次限制第 49-53 行permission接受字符串或字符串数组内部按逗号/空格拆分并去重readStringOrStringArrayFromConfig第 72-110 行permissionAttribute下只允许action键且取值只能是create、read、update、delete第 119-149 行。验证通过后的限制语义体现在ExternalAuthTokenHandler.verifyToken第 162-198 行若该令牌配置了accessRestrictions则当前插件必须在限制列表内否则抛出This tokens access is restricted to plugin(s) ...命中后把本插件对应的accessRestrictions与完整的allAccessRestrictions一并返回供DefaultAuthService写入凭证——这与上节getPluginRequestToken中“受限服务主体不可委托”的校验共同构成了外部令牌的越权防线。PluginTokenHandler 与装饰器扩展点API 报告中的PluginTokenHandler接口定义见 plugin/PluginTokenHandler.tsissueToken(options: { pluginId: string; targetPluginId: string; onBehalfOf?: { limitedUserToken: string; expiresAt: Date }; }): Promise{ token: string }; verifyToken(token: string): Promise { subject: string; limitedUserToken?: string } | undefined ;其默认实现DefaultPluginTokenHandler的令牌签名密钥由pluginKeySource提供。createPluginKeySource.ts 根据配置选择 DatabasePluginKeySource默认密钥对存于数据库对应 report-auth.sql.md 描述的authschema或 StaticConfigPluginKeySource静态密钥对适合只读数据库副本等场景。静态密钥方案需按官方文档用openssl生成 ES256 密钥对并配置backend: auth: pluginKeyStore: type: static static: keys: - publicKeyFile: /absolute/path/to/public.key privateKeyFile: /absolute/path/to/private.key keyId: some-custom-idkeys为数组即支持滚动轮换首项用于签名后续项参与验证见 service-to-service-auth.md 的说明。pluginTokenHandlerDecoratorServiceRefauthServiceFactory.ts 第 38-50 行则提供了一个单例装饰器扩展点export const pluginTokenHandlerDecoratorServiceRef createServiceRef (defaultImplementation: PluginTokenHandler) PluginTokenHandler ({ id: core.auth.pluginTokenHandlerDecorator, defaultFactory: async service createServiceFactory({ service, deps: {}, factory: async () { return impl impl; // 默认装饰器恒等不做任何修改 }, }), });默认工厂返回恒等函数即不改变行为当多个插件需要给所有插件的令牌签发/验证加上统一逻辑如审计、附加声明时可注册该 ref 的 factory 返回自定义包装函数。authServiceFactory在第 100-108 行将默认实现包裹进装饰器后再交给DefaultAuthService这正是 API 报告中该 ref 类型为(defaultImplementation: PluginTokenHandler) PluginTokenHandler的原因。配置项速查配置键位置说明backend.auth.externalAccessapp-config外部访问条目数组type取static/jwks/legacy或自定义处理器类型每条含options与可选accessRestrictionsbackend.auth.keysapp-config已弃用旧版静态令牌配置等价于legacy条目运行时打印弃用警告backend.auth.dangerouslyDisableDefaultAuthPolicyapp-config默认false置true后禁用插件间令牌的签发与验证后端将不要求认证仅在受控网络环境下作为最后手段backend.auth.pluginKeyStoreapp-configtype: static时使用静态 ES256 密钥对替代数据库密钥存储keys数组首项用于签名、其余参与验证小结从这份 API 报告出发可以看清backstage/backend-defaults认证入口的设计意图authServiceFactory提供零配置的默认认证行为插件令牌自动签发、用户令牌验证、外部令牌兜底同时通过两个可注入点保持开放——externalTokenHandlersServiceRefmultiton允许插件级注册任意自定义外部令牌处理器pluginTokenHandlerDecoratorServiceRefsingleton允许对默认插件令牌处理器做全局装饰。自定义处理器只需实现type/initialize/verifyToken三要素即可通过backend.auth.externalAccess配置段接入并借助accessRestrictions把外部主体的访问面精确限定到具体插件、权限名与权限属性。所有上述行为均可在 authServiceFactory 测试、DefaultAuthService 测试 同目录的测试文件及 ExternalAuthTokenHandler 测试 中得到印证配合 Service-to-Service Auth 文档 可按图索骥地落地到自己的部署中。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

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