ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Backstage 自定义认证 Provider 模块开发指南:基于 auth-backend 扩展新认证方式

Backstage 自定义认证 Provider 模块开发指南:基于 auth-backend 扩展新认证方式 Backstage 自定义认证 Provider 模块开发指南基于 auth-backend 扩展新认证方式【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文是一份面向 Backstage 贡献者与平台开发者的技术指南讲解如何为auth-backend添加全新的外部认证 Provider如自建 OAuth 服务、企业内部 SSO 或反向代理认证。文章以官方文档 docs/auth/add-auth-provider.md 为核心骨架结合当前仓库中auth-backend、plugin-auth-node与各内置认证模块的真实源码深入剖析认证流程、核心接口与完整落地步骤。读完本文你将掌握AuthProviderRouteHandlers接口模型、OAuthEnvironmentHandler多环境机制、Passport 策略封装方式以及从yarn new创建模块到接入后端、调试验证的全流程实战能力。认证Authentication是如何工作的Backstage 应用本身不直接持有用户凭证而是通过接入各种外部认证提供者来完成身份验证。在auth-backend中每个外部 Provider 都被包装成一个实现了AuthProviderRouteHandlers接口的对象。该接口定义于 plugins/auth-node/src/types.ts由四个方法构成每个方法默认挂载在/api/auth/[provider]/method形式的端点上/auth/[provider]/start - 从网页发起一次登录 /auth/[provider]/handler/frame - 处理一次已完成的外部认证操作回调 /auth/[provider]/refresh - 刷新一次登录的有效性 /auth/[provider]/logout - 登出已登录的用户其中refresh与logout是可选方法接口中声明为refresh?、logout?只有 Provider 支持时才被挂载。整个登录流程如下用户尝试登录前端打开一个弹出窗口popup指向auth端点。该端点先完成一些初始准备如写入 nonce cookie、拼接 state 参数然后在弹窗内将用户重定向到外部认证方外部认证方验证用户身份并把验证结果成功或失败返回给包装器的handler/frame端点handler/frame渲染出的网页向打开弹窗的父页面发出适当的响应随后弹窗关闭用户点击界面上的登出入口网页向logout端点发出请求完成登出。这段流程在源码中有着完整的落点bindProviderRouters函数见 plugins/auth-backend/src/providers/router.ts会为每个已注册 Provider 逐一创建子路由并绑定方法r.get(/start, provider.start.bind(provider)); r.get(/handler/frame, provider.frameHandler.bind(provider)); r.post(/handler/frame, provider.frameHandler.bind(provider)); if (provider.logout) { r.post(/logout, provider.logout.bind(provider)); } if (provider.refresh) { r.get(/refresh, provider.refresh.bind(provider)); r.post(/refresh, provider.refresh.bind(provider)); } targetRouter.use(/${providerId}, r);可以确认start只暴露 GEThandler/frame同时支持 GET 与 POSTlogout为 POSTrefresh在支持时同时暴露 GET 与 POST。若配置缺失bindProviderRouters还会注册一个抛出NotFoundError的兜底路由提示auth.providers.providerId配置缺失或环境变量未定义见 plugins/auth-backend/src/providers/router.ts。核心接口AuthProviderRouteHandlers任何认证包装器都必须实现AuthProviderRouteHandlers接口plugins/auth-node/src/types.ts。接口对四个方法做了明确约定start(req, res)处理start路由发起签名请求。请求可携带可选scopes响应为重定向到外部认证方同时写入 nonce cookie 并把 nonce 作为state查询参数带在重定向 URL 中frameHandler(req, res)外部认证方完成登录或授权后重定向到callbackURL由该方法处理。请求需携带 nonce cookie 与state参数响应通过postMessage向父窗口发送包含accessToken、expiresInSeconds、idToken、scope等信息的载荷若 Provider 支持刷新令牌还会设置 refresh token cookierefresh?(req, res)可选。用于在持有 refresh token cookie 时换取新的访问令牌也可被代理类 Provider 用于按需创建新会话logout?(req, res)可选。处理登出请求移除 refresh token cookie。登录发起与回调start 与 frameHandler发起登录时前端会打开一个弹窗将登录请求发往由start方法处理的/start端点。start将用户重定向到外部认证方认证方验证后把请求重定向回/handler/frame端点由frameHandler方法接手。frameHandler返回一个 HTML 响应其中包含一段脚本通过postMessage把请求结果发送给前端窗口。这条消息的类型就是WebMessageResponse定义于 plugins/auth-node/src/flow/sendWebMessageResponse.tsexport type WebMessageResponse | { type: authorization_response; response: ClientAuthResponseunknown; } | { type: authorization_response; error: Error; };注意官方文档中提到的postMessageResponse工具函数在当前仓库中的实际实现名为sendWebMessageResponse。它封装了生成postMessage响应的全部逻辑负责正确处理 CORS接收express.Response、WebMessageResponse以及前端地址appOrigin最终返回内嵌脚本与消息的 HTML 页面。实现中有两个值得关注的细节见 plugins/auth-node/src/flow/sendWebMessageResponse.ts数据会经过safelyEncodeURIComponent编码除常规编码外还会把替换为%27防止注入恶意脚本由于postMessage在 targetOrigin 被拒绝时会静默失败脚本会先以*为 targetOrigin 发送一条config_info类型消息告知目标 origin再以appOrigin为 targetOrigin 发送真正的授权响应。若父窗口收到第一条消息却始终等不到第二条即可判定 targetOrigin 被拒绝属于配置问题。最终响应会带上X-Frame-Options: sameorigin与基于 SHA-256 哈希的 CSPscript-src指令以缓解跨站风险。认证环境env隔离env概念是 auth-backend 工作方式的核心。它通过env查询参数标识应用运行的环境development、staging、production等同一运行时可以同时服务多个环境并根据请求中的env参数分派到对应的处理器。OAuthEnvironmentHandlerplugins/auth-node/src/oauth/OAuthEnvironmentHandler.ts是OAuthHandlers的实用包装器它实现AuthProviderRouteHandlers接口同时支持多个env。从源码看getEnvFromRequest会先从req.query.env读取环境取不到时再尝试从state参数解码出的 OAuth 状态中提取decodeOAuthState最后在getProviderForEnv中按环境查找处理器环境缺失或未配置会分别抛出InputError与NotFoundError。要实例化同一 Provider 在不同环境下的多个实例请使用OAuthEnvironmentHandler.mapConfig。它遍历环境名 → 配置的配置对象把每个环境的配置块分别交给工厂函数。给定如下配置development: clientId: abc clientSecret: secret production: clientId: xyz clientSecret: supersecretOAuthEnvironmentHandler.mapConfig(config, envConfig ...)会按顶层development与production键拆分配置把每一块作为envConfig传入回调。源码实现plugins/auth-node/src/oauth/OAuthEnvironmentHandler.ts即遍历config.keys()并为每个环境调用一次factoryFunc。AuthProviderFactory则是需要实现的工厂函数为给定 Provider 生成AuthProviderRouteHandlers。当前仓库中所有受支持的 Provider 都提供了一个返回OAuthEnvironmentHandler的AuthProviderFactory从而能同时处理多个环境的认证。为什么选择 PassportBackstage 选用了 Passport 作为认证平台原因是它拥有覆盖面极广的认证策略strategy生态。在实现自定义 Provider 时可以直接复用 Passport 社区现有的策略包再通过PassportOAuthAuthenticatorHelper将其无缝接入 Backstage 的认证框架大幅降低实现成本。如何添加一个新的策略 Provider快速指南根据需求新建一个认证 Provider 模块OAuth 类型或创建一个基于代理proxy认证的 Provider把新模块接入后端packages/backend/src/index.ts。创建新的认证 Provider 模块以虚构的服务foobar为例。使用yarn new创建新模块选择backend-module模板插件 ID 填auth-backend模块 ID 填foobar-provider。确保模块把对应的 passport provider 声明为依赖cd plugins/auth-backend-backend-module-foobar-provider yarn add passport-provider-a yarn add types/passport-provider-a实现 OAuth 类型的 Provider定义后端模块新模块通过authProvidersExtensionPoint扩展 auth-backend。扩展点接口定义于 plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts其registerProvider接收{ providerId, factory }而auth-backend侧plugins/auth-backend/src/authPlugin.ts注册该扩展点时会对重复的providerId抛出错误保证 Provider 标识唯一。import { createBackendModule } from backstage/backend-plugin-api; import { authProvidersExtensionPoint, commonSignInResolvers, createOAuthProviderFactory, } from backstage/plugin-auth-node; import { providerAuthenticator } from ./authenticator; /** public */ export const authModuleFoobarProvider createBackendModule({ pluginId: auth, moduleId: foobar, register(reg) { reg.registerInit({ deps: { providers: authProvidersExtensionPoint, }, async init({ providers }) { providers.registerProvider({ providerId: foobar, factory: createOAuthProviderFactory({ authenticator: providerAuthenticator, signInResolverFactories: { ...commonSignInResolvers, }, }), }); }, }); }, });createOAuthProviderFactory会基于 authenticator 生成一个标准的AuthProviderFactory并自动完成多环境包装commonSignInResolvers提供了通用的登录解析器如emailMatchingUserEntityAnnotation、emailLocalPartMatchingUserEntityName等见 plugins/auth-node/src/sign-in/commonSignInResolvers.ts用于把外部身份映射为 Backstage 目录中的用户。实现 authenticatorauthenticator 负责基于 Passport 策略创建策略实例并利用配置文件中的密钥clientId、clientSecret等驱动认证流程。它通过createOAuthAuthenticator创建并借助PassportOAuthAuthenticatorHelper复用通用的 start / authenticate / refresh 逻辑该 Helper 的from、defaultProfileTransform、start等实现见 plugins/auth-node/src/oauth/PassportOAuthAuthenticatorHelper.ts。import { Strategy as ProviderStrategy } from passport-provider-a; import { createOAuthAuthenticator, PassportOAuthAuthenticatorHelper, PassportOAuthDoneCallback, PassportProfile, } from backstage/plugin-auth-node; /** public */ export const providerAuthenticator createOAuthAuthenticator({ defaultProfileTransform: PassportOAuthAuthenticatorHelper.defaultProfileTransform, scopes: { // Scopes required by the provider required: [openid, email, profile, offline_access], }, initialize({ callbackUrl, config }) { const clientId config.getString(clientId); const clientSecret config.getString(clientSecret); return PassportOAuthAuthenticatorHelper.from( new ProviderStrategy( { clientID: clientId, clientSecret: clientSecret, // ... other options }, ( accessToken: string, refreshToken: string, params: any, fullProfile: PassportProfile, done: PassportOAuthDoneCallback, ) { done( undefined, { fullProfile, params, accessToken }, { refreshToken }, ); }, ), ); }, async start(input, helper) { return helper.start(input); }, async authenticate(input, helper) { return helper.authenticate(input); }, async refresh(input, helper) { return helper.refresh(input); }, });scopes.required声明了该 Provider 必需的权限范围PassportOAuthAuthenticatorHelper.defaultProfileTransform负责把 Passport 的全量 profile 转换为前端展示用的精简ProfileInfo。仓库中的真实实现示例当前仓库中已有多个同类实现可直接对照参考Googleplugins/auth-backend-module-google-provider/src/authenticator.tsGitHubplugins/auth-backend-module-github-provider/src/authenticator.ts其模块注册代码见plugins/auth-backend-module-github-provider/src/module.tsOktaplugins/auth-backend-module-okta-provider/src/authenticator.ts。以 GitHub 为例其 authenticatorplugins/auth-backend-module-github-provider/src/authenticator.ts除了读取clientId/clientSecret还会读取可选的enterpriseInstanceUrl配置并据此动态拼接authorizationUrl、tokenUrl、userProfileUrl——这展示了一个真实 Provider 如何在initialize中读取更多配置项、扩展策略选项。其模块plugins/auth-backend-module-github-provider/src/module.ts在注册时还额外合并了githubSignInResolvers与commonSignInResolvers。创建基于代理Proxy认证的 Provider代理认证 Provider 是指借用另一个外部认证方完成身份验证的 Provider例如 Google IAPIdentity-Aware Proxy或 AWS ALB。仓库中已内置支持这两者plugins/auth-backend-module-gcp-iap-provider与plugins/auth-backend-module-aws-alb-provider。其实现方式与 OAuth Provider 大体一致区别在于authenticator函数不同代理 Provider 不再走重定向到外部 OAuth 端点的流程而是由反向代理或网关注入身份头信息authenticator 负责从请求中解析并校验这些信息。需要新实现时可直接参照上述两个内置模块。Verify Callback策略需要一个所谓的 verify callback。verify callback 的目的是找出持有某组凭证的用户。当 Passport 认证一个请求时它会解析请求中包含的凭证然后以这些凭证为参数调用 verify callback……如果凭证有效verify callback 调用done向 Passport 提供完成认证的用户。如果凭证无效例如密码错误应调用done并传入false而非用户对象以表示认证失败。——引自 Passport 官方配置文档把新 Provider 接入后端添加新模块的方式与任何其他模块或后端插件相同。在packages/backend/src/index.ts中若该 Provider 仅用于内部安装则用内部导入路径backend.add(import(internal/plugin-auth-backend-module-foobar-provider));若模块已直接贡献给 Backstage则用官方命名空间backend.add(import(backstage/plugin-auth-backend-module-foobar-provider));完成注册后auth-backend会自动挂载以下端点对应 plugins/auth-backend/src/providers/router.ts 中的绑定逻辑router.get(/auth/providerA/start); router.get(/auth/providerA/handler/frame); router.post(/auth/providerA/handler/frame); router.post(/auth/providerA/logout); router.get(/auth/providerA/refresh); // if supported router.post(/auth/providerA/refresh); // if supported可以看到每个端点都以/auth和 Provider 名称为前缀。配置示例在app-config.yaml的auth.providers下按环境提供配置即可启用参考仓库根目录 app-config.yaml 中的内置 Provider 写法auth: environment: development providers: foobar: development: clientId: ${AUTH_FOOBAR_CLIENT_ID} clientSecret: ${AUTH_FOOBAR_CLIENT_SECRET}结合前文OAuthEnvironmentHandler.mapConfig的机制auth.providers.foobar下的每个环境键development、production等都会生成一个独立的处理器而auth.environment决定当前运行时默认使用哪个环境。测试新 Provider模块接入后可以用 curl 发起一次登录来验证流程是否正常curl -i localhost:7007/api/auth/providerA/start预期会返回302重定向并带有Location头。把该头中的 URL 粘贴到浏览器中打开即可触发完整的授权流程——浏览器中会看到跳转到外部认证方、授权后回调并关闭弹窗的完整链路。若返回的是 404 或配置错误提示可检查auth.providers.providerId配置与环境变量是否就绪并查看后端启动日志中bindProviderRouters打印的Configuring auth provider: providerId记录。小结为 Backstage 添加新的认证 Provider本质上是三件事一是实现AuthProviderRouteHandlers接口或更常用地通过createOAuthProviderFactorycreateOAuthAuthenticator组合出工厂二是借助OAuthEnvironmentHandler.mapConfig让同一 Provider 优雅地支持多环境三是通过authProvidersExtensionPoint把模块注册进auth-backend。无论是接入 Passport 社区已有的 OAuth 策略还是实现基于 IAP / ALB 的代理认证上述路径都完全一致仓库中 Google、GitHub、Okta、GCP IAP、AWS ALB 等内置模块即是可复刻的最佳范本。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表