Skip to main content
Re:Linked

用 Auth0 玩儿基于 OpenID Connect 的 SSO

· #trytrytry · 约 8.8k 字 · 浏览量: ...

缘起

突然想搞一个统一登录系统。一个原因是,托管的各个应用上帐号的密码不一样(废话!),管理起来稍显麻烦;另一个原因是,使用这些应用的时候,需要在每个应用都登录一次,用起来也比较麻烦。

好麻烦,不干啦!働きたくない!不过据说搞个什么 SSO 的能解决这个问题,那么我们也来搞一个吧。

所以,这篇文章的主题是利用 Auth0 这样的 IdP (Identity Provider) 平台,使得用户可以使用同一个帐号在各个 selfhost 的应用上登录。另外,由于登录都通过 Auth0,只要登录了一个应用一次,在 Auth0 的登录过期之前都可以使用 Auth0 的登录 session 直接登录到其它应用,达成所谓的 SSO (Single Sign-On)。

下文中使用「应用」一词表示通过 SSO 登录到的网站(spec 中的 "Relying Party",例如 Miniflux),使用 IdP 一词表示提供 SSO 登录功能的服务(spec 中的 "OpenID Provider",例如 Auth0)。

考虑

在配置 SSO 之前,需要决定自己预期的 SSO 行为。在默认配置下,可能会出现在应用中为同一位使用者创建了多个账户,或者为在应用中本不应拥有账户的使用者创建了账户的情况。因此,考虑预期 SSO 行为和系统安全息息相关。

安全性

在配置一个可以通过 OpenID Connect (OIDC) 使用 IdP 提供的帐号登录的应用的时候,这几件事可能需要仔细地考虑:

用户的注册

  • IdP 是否应该允许用户注册?对于面向消费者的服务,这一般是肯定的。但对于面向组织内部成员的服务来说,允许用户自行注册通常不是一个好办法;常见的策略是由管理员手工为用户创建 IdP 账户。
    • 在 Auth0 中,可以通过给 Username-Password-Authentication 的 Settings 配置 "Disable Sign Ups" 选项配置。
  • 应用要不要允许通过 OIDC 登录的用户在应用上创建账户?许多组织可能会允许通过 OIDC 登录的用户在应用自行创建账户,这样就不必在创建新用户之后手工在各个应用创建此用户的账户。例如,在新员工入职的时候,员工的姓名和邮件地址都被写入 IdP 中;在员工登录 Slack 的时候,Slack 就可以通过 ID token 中的这些信息自动创建员工 Slack 账户,并为其管理 Slack 账户的显示名。

用户身份的关联

应用如何通过 ID token 提供的信息,决定让用户登录到哪个帐号?常见的关联方法大概有这么几种:

  • 手工绑定(例如 Miniflux):ID token 中会有一个 sub (Subject) claim ,是 IdP 给这个应用提供的用来标识用户的唯一值。只要让已在应用中登录的用户通过 OIDC 登录,记录 ID token 中的这个 sub 值,就可以关联应用中的用户和 IdP 中的用户。
  • 通过 email 地址匹配(例如 Gitea):ID token 中经常会有 email claim(在 OIDC 流程的 scope 中申请了 email scope 的话),写着这个 IdP 中账户的 email 地址。把这个 email 地址和应用账户列表中的 email 字段对比,就可能找到零个、一个或多个可以用来登录的账户。在这种情况下,需要特别注意 IdP 提供的 email 地址是否已被验证属于 IdP 用户[1]
  • 通过其它 claim 匹配(例如 Nextcloud 的官方 OIDC 登录插件):管理员可以将应用账户的识别信息写入 IdP 的数据库中,再通过让 IdP 在返回的 ID token 中加入此信息作为额外的 claim,来让应用找到需要关联或者登录的应用账户。

2FA/MFA 放在哪?

二步验证 (2-Factor Authentication) 或多步验证 (Multi-Factor Authentication) 已经成为现代用户管理系统的必备项。但在 OIDC 中,这个二步验证是放在 IdP 侧还是应用侧(或者都配置?),可能会需要一些考虑:

优点缺点
放在 IdP 侧配置统一方便
无需应用兼容
可以使用 IdP 提供的用户交互验证应用
应用对 IdP 完全信任
放在应用侧即使 IdP 被骇,MFA 依旧可以保护应用帐号安全配置麻烦
不是所有应用都支持 MFA
IdP 配置了 MFA 的情况下,应用可能不支持跳过 MFA

IdP 的角色

IdP 可以作为 OpenID Provider,为其它应用提供 OIDC 服务;又因为 IdP 也需要登录用户,它也可以作为 Relying Party,通过另外的 IdP 来登录(例如社交帐号登录)。所以,在配置 IdP 时,需要确定 IdP 担当的角色,它是用户数据的提供者,帮助用户登录到其它应用;还是用户数据的消费者,需要访问其它的 IdP 平台以允许用户登录?两边各举一个例子:

  • 当用户通过 Auth0 登录到 NextCloud 时,Auth0 的角色是 OpenID Provider。NextCloud 通过验证和读取 Auth0 提供的 ID token 来决定是否允许用户登录到哪个 NextCloud 账户。
  • 当用户通过 GitHub 登录到 Auth0 时,Auth0 的角色是 Relying Party。Auth0 通过读取 GitHub 提供的 ID token 来决定提供给用户的 ID token 应该使用 Auth0 哪个账户的信息。

IDaaS 服务的选择

(这里的前提是「如果不自建的话」;打算搭 Keycloak 的话可以跳过此段落。)

简而言之:Azure AD (Entra ID) 是经典玩法,账户系统在 AD/AAD 的可以优先考虑;Auth0 好看功能又多,但 MFA 要加钱;Okta 免费的开发者套餐支持 MFA(例如 Okta 自己的 Okta Verify),但只支持 5 个应用。IdP 护城河算不上深,也有很多小厂商在做,但请深刻考虑对它们的信任,因为密码学并不能阻止它们随意登录到你的系统。

做 IDaaS (Identity as a Service) 的厂商太多啦。Auth0 是最出名的 IDaaS 厂商之一(被 Okta 收购了),其它的大型厂商也有不少:OktaCisco DuoPingIdentity,还有 OneLogin 等等。另外,还有或许和前面几家公司的服务相比要远远更加出名的 Azure Active Directory (Microsoft Entra ID)。通过在 Entra ID 创建一个 application,第三方应用可以非常方便地配置 Microsoft 账户(包括个人账户和公司/学校账户)的社交平台登录支持。

特别地,关于 Okta

Okta 确实被许多大型跨国企业所信任,但笔者觉得读者需要知道近几年的 Okta 并不安宁:

  • 2022 年三月,$LAPSUS 宣称成功入侵 Okta 系统(甚至还有截图!)。Okta 的官方声明说,这是当年一月 Okta 的外包客户支持商 Sitel 雇员的账户被骇,而 Okta 一月就发现了这个问题。对于直到 $LAPSUS 发布入侵截图之后的三月下旬才出面解释的行为,Okta 在声明中也承认拖延公告安全事件是「犯了个错误」。
  • 当年十二月,Okta 发布公告称被 GitHub 通知代码库受到未授权访问。公告提到称代码库泄露不会影响服务的安全性。至于为什么代码库会泄露,Okta 的公告里只字未提。
  • 2023 年十月,Cloudflare 称发现其 Okta 服务被骇。Cloudflare 的文章中提到,Okta 又一次没有尽快向用户披露安全事件,甚至修复漏洞都用了两个多星期。

Okta, are you Ok?

实装

配置应用通过 Auth0 登录

除了 OIDC 之外,另一个常用的方案是 SAML。SAML 较为复杂且过于企业级,本文暂不提及。

OAuth 2.0 是一个授权 (AuthZ, authorization,「确定客户端是否有权限」) 协议,而 OIDC (OpenID Connect) 是基于 OAuth 2.0 之上的认证 (AuthN, authentication,「确定客户端是谁」) 协议。阮一峰有一篇简单的文章和一篇复杂的文章,它们对 OAuth 的解释都不错。Microsoft 的文档也尚且能看。Auth0 还有一个超棒的 OIDC(以及其它 OAuth 流程)的 playground,可以直观地展示 OIDC 的流程。

简而言之,一般的流程是:

  1. 在 IdP 创建 application,获得 Client ID、Client Secret 和 OIDC Auto Discovery URL 等
  2. 在应用创建 OIDC 登录方式,填入上述值,并获得 Callback URL
  3. 在 IdP 的 application 填入 Callback URL
  4. 搞定!

也就是说,通常有这些必要的信息:

应用端配置(IdP 提供):

名称注释示例
Client ID(有时也称作 App ID)这是一个用来代表应用的值。虽然经常是 UUID 的格式(例如在 Azure AD),但也有是其它格式的时候。ec685f7d-0aac-4585-9ad4-df6bdaf180b6
Client Secret这是应用用来证明自己身份的值。正如名字所说的一样需要保密。(略)
ScopeOAuth2 中表示应用所申请权限的值。在 OIDC 中通常是右边这三个。openid email profile
OIDC Auto Discovery URL Authorization/Token/Certificate Endpoint应用在 OIDC 过程中需要访问的几个端点。两种配置项择一配置即可,应用可能会只支持配置其中一种或均会支持。详情见下文。

关于 OIDC Auto Discovery URL 及 Authorization/Token/Certificate Endpoint

在 OIDC 的过程中,应用需要访问 Authorization Endpoint 以使用户验证身份,访问 Token Endpoint 以获取 ID token,以及访问 Certificate Endpoint 以验证 ID token 的有效性。为了方便起见,OIDC 标准中提供了 OpenID Connect Discovery。实现了 Discovery 的 IdP 会提供一个包含上述信息的文档(称作 OpenID Provider Configuration),放置在 {issuer}/.well-known/openid-configuration,其中 {issuer} 是 token 的 iss claim 的值。

在 Auth0,这些 URL 可以在应用设置页最下方 "Advanced Settings" 的 "Endpoints" 选项卡中找到。它们在那里分别叫做 "OpenID Configuration" 和 "OAuth Authorization URL"、"OAuth Token URL"、"JSON Web Key Set"。

这些 URL 的示例:

IdP 端配置(应用提供):

名称注释示例
Callback URL这是 IdP 验证用户身份成功后,携带 ID token(或用于兑换 ID Token 的 Authorization Code)跳转回的 URL。这个 URL 通常由应用决定,但需要在 IdP 端配置。IdP 端会验证传入 Authorization Endpoint 的 Callback URL 是否在自己的已知列表中。https://gitea.example.com/user/oauth2/msauth/callback

给 Auth0 添加社交平台(例如 GitHub)登录支持

在 Auth0,第三方平台登录是通过配置 Social Connections 完成的。

Auth0 不支持禁止通过社交平台注册。如果希望只允许社交平台登录到已有的 IdP 账户,需要配置帐号链接,将通过社交平台登录的 IdP 账户链接到其它数据源的主 IdP 账户,并确保主 IdP 账户是社交平台数据源的用户无法完成获得 ID token 的流程。

步骤和上面差不多,只是进行操作的站点反过来:

  1. 在 GitHub 创建 application,填入 Application name、Homepage URL 和 Authorization callback URL,获得 Client ID 和 Client Secret
    • GitHub 的 OAuth app 可以挂靠在用户帐号下方(在这里创建),也可以挂靠在组织下方(在 https://github.com/organizations/[org-name]/settings/applications 创建)
    • 填入值的示例:
      • Application name: My Auth0
      • Homepage URL: https://example.jp.auth0.com
      • Authorization callback URL: https://example.jp.auth0.com/login/callback
    • 获得值的示例:
      • Client ID: Ov23livDwfjz72TrrCgo
      • 点击 "Generate a new client secret",得到 Client Secret: 95ea5ac40e17d3aba502fadbd37c15bf4b212d42
  2. 在 Auth0 的 Authentication > Social 页面下创建 GitHub 社交平台登录方式,填入上述值,保存
  3. 搞定!现在就可以点击 "Try Connection" 尝试登录了。

  1. email claim 中的邮件地址不一定是经过 IdP 服务验证的。如果 email 未被验证就被应用信任,可能会导致应用内的账户被第三方 takeover。 ↩︎

评论

填写邮箱即表明您同意接收关于评论回复的邮件通知。


LIKE