ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Ghost SSO 适配器开发指南:基于 @tryghost/adapter-base-sso 实现外部身份提供商登录

Ghost SSO 适配器开发指南:基于 @tryghost/adapter-base-sso 实现外部身份提供商登录 Ghost SSO 适配器开发指南基于 tryghost/adapter-base-sso 实现外部身份提供商登录【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost本篇技术文章围绕 Ghost 仓库中的 adapter-base-sso 包 展开讲清楚 Ghost SSO 适配器的整体定位、SSOBase基类的契约方法、用户仓库注入机制以及如何将一个外部身份提供商如自研 token 校验服务接入 Ghost 管理后台的登录流程。读完本文你可以独立实现并激活一个 SSO 适配器并理解 Ghost 核心在会话中间件层面如何调用适配器的三个阶段方法。一、SSO 适配器在 Ghost 中的定位Ghost 通过一套“适配器adapter”机制允许外部实现替换内置功能storage、scheduling、cache、redirects 等。SSO 是其中一类特殊适配器它的作用是让外部身份提供商完成 Ghost 管理后台的登录从入站的 Express 请求中读取凭证把凭证解析为一个身份identity再把这个身份映射为一个 Ghost 用户。从源码结构看SSO 适配器与其他适配器一样由 Ghost 核心的 base-classes.ts 统一登记基类映射其中sso: SSOBase声明了所有 SSO 适配器必须继承的基类export const baseClasses { storage: StorageBase, scheduling: SchedulingBase, sso: SSOBase, cache: CacheBase, redirects: RedirectsStoreBase, route-settings: RouteSettingsStoreBase, jobs: JobsBackendBase, } satisfies BaseClassMap;该文件注释还指出这一映射同时被bin/validate-adapters.js在构建期用于校验适配器实现因此baseClasses是基类映射的唯一声明位置。而 Ghost 核心自带的默认实现 DefaultSSOAdapter.ts 三个方法全部返回null——也就是说未激活自定义适配器时SSO 登录链路是“空转”的等待你的适配器注入真实逻辑。二、SSOBase 契约三个必须实现的方法SSOBase定义在 base.ts是一个带两个类型参数Token与Lookup的抽象类分别对应“请求凭证”和“身份查找键”的任意自定义类型export abstract class SSOBaseToken, Lookup implements SSOAdapterToken, Lookup { declare readonly requiredFns: readonly [ getRequestCredentials, getIdentityFromCredentials, getUserForIdentity, ]; abstract getRequestCredentials(request: Request): PromiseToken | null; abstract getIdentityFromCredentials(credentials: Token): PromiseLookup | null; abstract getUserForIdentity(identity: Lookup): PromiseUser | null; }三个抽象方法构成一条清晰的三段式解析链getRequestCredentials(request)— 从入站的 Express 请求中取出凭证token、cookie 等请求中不存在凭证时返回nullgetIdentityFromCredentials(credentials)— 校验凭证并将其解析为一个身份查找键无法验证时返回nullgetUserForIdentity(identity)— 把身份映射为 Ghost 用户User接口即{ id: string; email: string }无匹配用户时返回null。任何一步返回null登录流程都会在该阶段安全终止不会把无效凭证泄漏到下一环节。requiredFns属性在构造函数中通过Object.defineProperty被设置为冻结的只读数组Object.freeze这是 Ghost 适配器体系的通用约定框架在加载适配器时会检查实现是否覆盖了所有requiredFns列出的方法。该行为由 index.test.ts 中的断言验证assert.deepEqual(store.requiredFns, [ getRequestCredentials, getIdentityFromCredentials, getUserForIdentity, ]); assert.ok(Object.isFrozen(store.requiredFns), requiredFns should be frozen);三、用户仓库注入不直接依赖 Ghost 模型层SSO 适配器最终需要把一个身份落到某个 Ghost 用户上但基类刻意不让适配器直接requireGhost 的模型层。为此SSOBase定义了UserRepository接口并在构造后由 Ghost 核心注入export interface UserRepository { /** Look up a single user by their email address, or null if none exists. */ getByEmail(email: string): PromiseUser | null; /** Look up the site owner user, or null if none exists. */ getOwner(): PromiseUser | null; }基类通过私有字段#userRepository持有注入实现并暴露两个 protected 辅助方法给子类使用getUserByEmail(email)— 按邮箱查找单个用户委托给#users.getByEmail(email)getOwnerUser()— 查找站点 Owner 用户委托给#users.getOwner()。若 Ghost 在适配器被使用之前没有调用注入方法任何辅助方法都会抛出IncorrectUsageError来自tryghost/errors错误信息明确提示 “Ghost must call setUserRepository() before the adapter is used”。测试用例 验证了两种行为注入仓库后lookupByEmail(janeexample.com)返回对应用户查不到的邮箱返回nulllookupOwner()返回 Owner未注入仓库时lookupByEmail与lookupOwner均 reject错误匹配/no user repository configured/。Ghost 核心侧的注入点在 session/index.js 中可以看到 Ghost 核心实际如何完成注入与会话创建module.exports.createSessionFromToken () { const ssoAdapter adapterManager.getAdapter(sso); ssoAdapter.setUserRepository({ async getByEmail(email) { const user await models.User.findOne({ email }); return user ? { id: user.id, email: user.get(email) } : null; }, async getOwner() { const owner await models.User.findOne({ role: Owner, status: all }); return owner ? { id: owner.id, email: owner.get(email) } : null; }, }); return sessionFromToken({ callNextWithError: false, createSession: sessionService.createVerifiedSessionForUser, findUserByLookup: ssoAdapter.getUserForIdentity.bind(ssoAdapter), getLookupFromToken: ssoAdapter.getIdentityFromCredentials.bind(ssoAdapter), getTokenFromRequest: ssoAdapter.getRequestCredentials.bind(ssoAdapter), }); };这段代码印证了基类文档的声明模型耦合被收敛在 Ghost 核心一侧getByEmail背后是models.User.findOne适配器实现只需要使用基类提供的辅助方法同时sessionFromToken的调用参数与SSOBase的三段式方法一一对应——getTokenFromRequest、getLookupFromToken、findUserByLookup分别绑定到getRequestCredentials、getIdentityFromCredentials、getUserForIdentity构成了完整的登录解析链路。四、实现一个完整的 SSO 适配器按照 包 README 给出的最小完整示例安装基类后继承SSOBase并实现requiredFns列出的全部方法npm install tryghost/adapter-base-ssoconst {SSOBase} require(tryghost/adapter-base-sso); class MySSO extends SSOBase { async getRequestCredentials(request) { const token request.get(authorization); return token ?? null; } async getIdentityFromCredentials(token) { return this.verify(token); // your validation → identity, or null } async getUserForIdentity(identity) { // getUserByEmail is provided by the base class return this.getUserByEmail(identity.email); } } module.exports MySSO;在这个示例中凭证取自请求的authorization头verify(token)是留给你的外部身份验证逻辑例如向你的身份提供商发起校验请求成功后返回一个包含email的身份对象最后通过基类提供的getUserByEmail完成到 Ghost 用户的映射。注意getUserByEmail是 protected 方法只能在子类内部调用这也是基类注释中强调的“不要绕过辅助方法直接依赖 Ghost 模型层”的原因。五、安装与激活适配器把适配器放在 Ghost 实例的content/adapters/sso/MySSO/index.js路径下并在 Ghost 配置中激活{ adapters: { sso: { active: MySSO, MySSO: {} } } }其中active字段指向要启用的适配器名MySSO键下的对象会作为配置参数传入适配器构造函数。加载与校验由 Ghost 核心的 adapter-manager 完成可通过adapterManager.getAdapter(sso)获取当前激活实例session 中间件正是这样拿到的。六、在 Ghost monorepo 中开发本包tryghost/adapter-base-sso是 Ghost monorepo 的 workspace 包当前版本 0.1.3见 package.json在仓库根目录可执行pnpm --filter tryghost/adapter-base-sso build # 用 tsc 编译到 build/ESM pnpm --filter tryghost/adapter-base-sso test # 类型检查 单元测试 pnpm --filter tryghost/adapter-base-sso dev # 变更后自动重编从 package.json 可以看到几个关键工程细节ESM-onlytype: module入口main指向build/index.jsexports中还提供source条件指向./src/index.ts供 monorepo 内部按源码消费编译方式build脚本就是裸的tsctest:types则用独立的 test/tsconfig.json 做tsc --noEmit类型检查依赖极少运行时仅依赖tryghost/errors用于抛出IncorrectUsageError开发期依赖包括 vitest、eslint 与internal/cfg-*内部配置包。该包是 ESM-only 且使用tscmodule: nodenext编译tsconfig.json 继承自internal/cfg-typescript/esm.json并将src编译到build。README 特别强调src/中的相对导入必须显式带扩展名且要写真实的.ts扩展名——如import {x} from ./x.ts——tsc在输出时会通过rewriteRelativeImportExtensions将其改写为.js。src/index.ts 正是一个例子全文只有一行export * from ./base.ts;包入口再经 index.ts 导出SSOBase、User、UserRepository、SSOAdapter等类型build/目录files字段限定 npm 只发布build即为发布产物。七、小结tryghost/adapter-base-sso用一个不到百行的抽象类定义了 Ghost SSO 适配器的完整契约三段式解析链请求凭证 → 身份 → Ghost 用户、冻结的requiredFns声明以及由 Ghost 核心在构造后注入的UserRepositorygetUserByEmail/getOwnerUser。适配器实现者只需关注凭证校验与身份映射用户查找全部经由基类辅助方法完成Ghost 核心侧则通过 adapter-manager 的基类映射 和 session 中间件的注入点 完成装配。实现、激活并在测试中验证注入行为即可以这套基类把任意外部身份提供商接入 Ghost 管理后台的登录流程。【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表