为 Discourse 添加新的“托管”身份验证方法

承接自 Future Social Authentication Improvements

我们目前正在将所有“关联账户”信息迁移到一个单一的数据库表中。这将有助于显著减少重复的逻辑,并允许未来更快速的开发。例如,将我们的核心 Twitter 逻辑迁移到新系统迁移我们的核心 Twitter 逻辑后,代码行数从 136 行减少到仅 24 行 :tada:

此帖子并非旨在成为添加新身份验证提供程序的逐步操作手册,但它将旨在提供一个概述,并在必要时指向相关的源代码。

实现一个身份验证器

每个身份验证器必须实现 Auth::Authenticator 的一个子类 Auth::Authenticator。要使用新的共享逻辑,身份验证器可以扩展 Auth::ManagedAuthenticator。可以在核心 Facebook 身份验证器中找到一个基本实现的示例:

实现类必须重写 nameenabled?register_middleware

:information_source: 附注: 为了兼容多站点,重要的是任何特定于站点的 信息 都在 setup lambda 中提供给 omniauth,而不是在定义时固定。请参阅所有核心身份验证器以获取此类示例。

所有将外部账户链接到 Discourse 账户的逻辑都由 Auth::ManagedAuthenticator 处理。这依赖于 omniauth 提供商返回的数据格式,该格式在其文档中定义。如果需要对这些数据进行任何操作,身份验证器可以覆盖 after_authenticate 方法,并根据需要操作 auth_token。例如,核心 Twitter 身份验证器会从 token 中删除所有 extra 信息:

数据存储在 user_associated_accounts 数据库表中。provider_uidinfocredentialsextra 都直接取自 omniauth 返回的数据。

定义了 Authenticator 类后,需要对其进行注册。这必须在应用程序生命周期的早期发生,并且不能发生在插件的 after_initialize 方法中。最小的注册可以只包含对身份验证器的引用。在插件中,可以使用 auth_provider 函数进行注册。例如:

auth_provider authenticator: OpenIDConnectAuthenticator.new()

在核心代码中,注册发生在 discourse.rb 中。可以在此处找到 AuthProvider 选项的完整列表。可以使用这些选项定义文本内容,但最好在 client.en.yml 中提供可本地化的字符串,遵循标准键。例如:

来自 @fantasticfears 的其他 ManagedAuthenticator 注释

ManagedAuthenticator 详解

您可能需要处理一些特殊情况的身份验证。并且您想了解更多关于 ManagedAuthenticator 的信息。基本上,它有几个操作、选项,并控制数据的用法。

Discourse 使用两个控制器管理用户信息。Users::OmniauthCallbacksController 在 OAuth2 身份验证完成后管理有效载荷。此时会调用 after_authenticatecan_connect_existing_user? 也会在这里使用。
您可以阅读一些私有方法来了解不同的数据字段是如何工作的。

if authenticator.can_connect_existing_user? && current_user
  @auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
else
  @auth_result = authenticator.after_authenticate(auth)
end

UsersControllerrevoke_account,它使用 can_revoke?revoke。但要使 revoke 方法能够远程工作,您需要构建自己的实现。

UserAuthenticator 是一个帮助身份验证(验证电子邮件确认或 OAuth2 路径)用户的服务类。after_create_account 在此处调用。

核心逻辑保留在 after_authenticate 中,使用 Auth::Result 数据类。我们遵循这里的数据结构。extra_data 将传递给 after_create_account 以创建相关记录。

result.extra_data = {
  provider: auth_token[:provider],
  uid: auth_token[:uid],
  info: auth_token[:info],
  extra: auth_token[:extra],
  credentials: auth_token[:credentials]
}

它将尝试匹配并连接到现有账户。
您可能想知道为什么可以自动创建账户,但没有 User.create。这在 UsersController#create 中完成。

authentication = UserAuthenticator.new(user, session)

user 是一个新实例,将由身份验证提供程序准备的会话数据填充。相信我,这只是魔法。


迁移到新系统

为了向新系统实现无缝切换,数据应从旧的存储位置迁移。对于核心身份验证提供程序,这可能是专用的表。对于插件,这可能是 plugin_store_rowsoauth2_user_infosuser_associated_accounts 行中所需的最小数据是 provider_nameprovider_uiduser_id。有关迁移示例,请参阅:

一旦 ManagedAuthenticator 系统在 v2.2.0 版本中发布到稳定分支,我们将开始迁移官方身份验证插件。届时,将在此处添加 plugin_store_row 迁移示例。


本文档已版本控制 - 在 github 上建议更改。

23 个赞

@david 这里完成的所有工作都太棒了。非常感谢。我也有机会玩了 https://github.com/discourse/discourse-development-auth,它非常方便。

只是有一个注意事项,该功能在本地与 ember cli 不能很好地协同工作(不会弹出注册弹窗)。我在编写一个用于添加身份验证提供程序的插件时挠头不已,突然想到使用 NO_EMBER_CLI=1,然后一切都开始正常工作了。

7 个赞

我想了解一下,实现一个身份验证器是否是正确的路径,可以参考:

我的理解对吗?所有已注册的身份验证器都会在应用程序早期被调用,因此我可以在其中测试 URL 是否包含用户名和电子邮件身份验证的提示,并响应渲染一个“向我发送登录链接”的表单?