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

继承自 Future Social Authentication Improvements

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

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

实现身份验证器

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

实现类必须覆盖 nameenabled?register_middleware

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

链接外部账户到 Discourse 账户的所有逻辑都由 Auth::ManagedAuthenticator 处理。这依赖于 omniauth 提供程序返回的数据格式,该格式在其文档中定义。如果需要对此数据进行任何操作,身份验证器可以覆盖 after_authenticate 方法,并根据需要操作 auth_token。例如,核心 Twitter 身份验证器会从令牌中删除所有 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

UsersController 中的 revoke_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)

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


迁移到新系统

为了无缝切换到新系统,数据应从旧的存储位置迁移。对于核心身份验证提供程序,这可能是专用的表。对于插件,这可能是 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 是否包含用户名和电子邮件身份验证的提示,并响应渲染一个“向我发送登录链接”的表单?