将 Discourse 用作身份提供者 (SSO, DiscourseConnect)

你好 :waving_hand:,我编写了这个 SimpleSAMLphp 认证模块,以便在 SimpleSAMLphp 安装中将 Discourse 用作 SSO 提供商。也就是说,您可以将 Discourse 用作任何支持 SAML 或 Shibboleth 认证的服务的 SSO 提供商,这非常棒。

请告诉我您的看法(如果您有兴趣对代码进行评论,可以使用 GitHub Issues)。

4 个赞

太棒了!如果您希望让该模块更受关注,可以在我们的 #plugin:extras 分类中创建一个相关主题。该分类是 Discourse 所有扩展和集成的目录,但不包括 Discourse 插件。

3 个赞

编辑:已添加原型图


我正在现有网站上实施此单点登录(SSO),只是想确认大家是如何向用户“展示”其登录方式的。

例如,假设我的网站 www.example.com 在顶部导航栏中有一个“登录”按钮。

该登录按钮是否应直接将用户跳转到我的 Discourse 登录认证页面?还是说,最好先显示一个信息页面或模态框,内容如下:

我只是想知道,如果不提前告知用户即将发生什么,他们是否会感到困惑?:thinking:

是否有人有相关的用户体验或最佳实践可以分享?

另外,也要感谢 @techAPJ 在本帖中发布的非常详细的首帖,我正是按照你的步骤从零开始成功在我的 ASP.NET 网站上实现了这一功能 :+1:

2 个赞

是否可以在其中包含一个状态参数,使其原样返回,就像 OAuth 中所做的那样?Asp.net Core 认证中间件依赖生成关联 ID 来防止 CSRF 攻击,而我目前还没有简便的方法来包含此 ID。

您能否在封装回调 URL 之前,将其添加到查询字符串中?

例如:

nonce=NONCE&return_sso_url=https://www.example.com/my/callback_url.aspx?myparam=here

@jessicah 我今天试过了,是的,它运行正常。

        ' 创建返回 URL
        Dim strReturnURL As String = "https://www.example.com/authtestRETURNURL.aspx?myownparametershere=surewhynot"

        ' 生成随机 nonce。暂时保存它,以便与返回的 nonce 值进行验证
        Dim strNonce As String = Guid.NewGuid().ToString("N")

        ' 创建包含 nonce 和返回 URL 的新负载(Discourse 将在验证后将用户重定向至此)
        '       负载应如下所示:nonce=NONCE&return_sso_url=RETURN_URL
        Dim strPayload As String = "nonce=" & strNonce & "&return_sso_url=" & strReturnURL

然后在被回调的页面上,您可以在解码后的 SSO 查询字符串中找到以下内容:

&return_sso_url=http%3A%2F%2Fwww.example.com%2FauthtestRETURNURL.aspx%3Fmyownparametershere%3Dsurewhynot&username=Rich

希望这正是您所指的内容?

1 个赞

多站点使用 discourse-auth-proxy 的方法?

是否有任何示例或建议,将 Discourse 用作多站点身份验证的 SSO 提供商?

似乎有两种基本的多站点方法:

  1. 为每个受保护的站点使用多个 discourse-auth-proxy 实例。
  2. 使用单个 discourse-auth-proxy 实例,使包含 return_sso_url 的有效载荷根据登录请求的来源而变化。

我认为这两种方法都可以行得通,但问题在于,您仍然需要登录到每个不同的站点。
此外,还存在风险:某些数据可能存储在 Postgres 中,并会被来自不同站点的每次登录覆盖。例如:site1.comsite2.com
(我不了解 Discourse 身份验证/PostgreSQL 架构的具体细节,所以不确定。)

最理想的情况是,只需登录一次,即可同时登录多站点组中的所有站点。例如:site1.comsite2.comsite3.com

据说 Stackoverflow 通过使用本地 Session 存储和 Iframe 作为主要实现方式做到了这一点。技术描述

但我非常希望了解是否有人已实施过任何使用 Discourse 作为 SSO 提供商的多站点登录方案。
方法 1:多个 discourse-auth-proxy 实例
方法 2:修改 discourse-auth-proxy,使其在有效载荷中影响 return_sso_url。
方法 3:实现方法 1 或 2,使得登录一次后,从 site1.com 切换到 site2.com 时无需再次登录。

我特意标记了您 @sam,因为您最初编写了 Go 语言的 discourse-auth-proxy 程序。

你好。

我在处理提供者如何在返回的 SSO URL 中处理 + 号的问题上卡住了。

在我的情况下,我的 DiscourseConnect 客户端会生成一个类似以下的 return_sso_url

  1. 原始字符串:http://example.com/foo/bar?wpLoginToken=123+\\
  2. PHP URL 编码后:http%3A%2F%2Fexample.com%2Ffoo%2Fbar%3FwpLoginToken%3D123%2B%5C

然后 DiscourseConnect 会返回到:

  1. http://example.com/foo/bar?wpLoginToken=123%20\\\u0026sso=foo\u0026sig=bar
  2. http://example.com/foo/bar?wpLoginToken=123+\\\u0026sso=foo\u0026sig=bar

问题在于,返回的 URL 将由 PHP 中的 urldecode 函数处理(这是 MediaWiki 认证的核心工作流程),导致 wpLoginToken 的值意外地从 123+\\ 变为 123 \\

我似乎已经找到了原因:

看来客户端和服务端在实现编码/解码时遵循了不同的规范。

我并不是要求 Discourse 做出更改,而是在寻求解决此问题的建议。

谢谢。

编辑:
我通过将 + 号进行两次 URL 编码解决了这个问题。

3 个赞

谢谢!

我正在尝试使用。我在 GitHub 上给你提交了两个拉取请求(PR),一个是更新文档,另一个是修复我遇到的一个 bug。

1 个赞

在“设置 > 登录”中,两个 DiscourseConnect provider 设置并非连续排列,而是与 DiscourseConnect 的其他设置交错显示:

由于 provider 设置与非 provider 设置分别对应相反的用途场景(即使用 Discourse 管理其他系统的用户, versus 使用其他系统管理 Discourse 的用户),将这些设置混合显示容易导致配置错误。如果两个 provider 设置能够连续排列,并完全置于非 provider 设置之前或之后,将减少混淆。

4 个赞

@techAPJ 这个能否与 AWS Cognito 配合使用?我想在 AWS Amplify 中为我的 Discourse 社区创建一个应用,并希望该应用通过 Discourse 进行身份验证。

1 个赞

抱歉回复晚了。您完全正确——它们这样交错排列确实令人困惑。我已在该 PR 中进行了整理:

6 个赞

@mdoggydog 感谢您最近更新了 DiscourseSsoConsumer MediaWiki 扩展。我们一直在琢磨如何处理用户在未退出 Discourse 的情况下退出我们 wiki 的问题,而 $wgPluggableAuth_EnableAutoLogin 绝对不是我们想要的,因为它阻止了对 wiki 的匿名访问。您添加的 $wgDiscourseSsoConsumer_AutoRelogin 设置正是我们所需要的。

1 个赞

你好:wave:!

我正在尝试使用原始帖子的 PHP 示例,但他们存储数据的方式没有意义。他们只是将值存储在 SQL 数据库中,键为 loginnonce。如果我想使用 SQL 来存储 nonces,我的 SQL 数据库应该是什么样的?

一些可能有助于我理解的额外信息是我的用途——我希望通过生成一个与他们的 UUID 相关联的 SSO 链接来将 Discourse 用户与 Minecraft 账户关联起来。在成功使用 Discourse 登录后,我将在一个表中存储他们的 UUID 和 Discourse ID。

到目前为止,我已经设法让 PHP 示例正常工作,但我似乎没有完全理解我必须如何修改它才能适用于我的用例。理想情况下,我想通过 GET 请求生成链接并将其发送给用户,这样 UUID 就已经与 nonce 相关联了。

感谢这篇帖子,没有它我将更加迷茫!

编辑:对于 nonces,我是否最好将 nonces 存储在表中并通过它进行查找?我知道我需要匹配 nonce,但除非我能在重定向 URL 中传递额外信息(我尚未成功做到这一点),否则我不确定如何正确引用 nonce。

2 个赞

我创建了一个指南,允许管理员通过 SimpleSAMLphp 将 DiscourseConnect 配置为标准的 SAML 协议。

1 个赞

链接详情

/session/sso_provider?sso=bm9uY2U9NDA3YTVlNjg1ZTEwMDlh...

我不确定为什么会出现 502 错误(网关错误)。

我编辑掉了你提供的 sso 参数的一些值。除非有人知道你的密钥,否则他们无法解码该值,但仍然提供完整值似乎不那么安全。这与你遇到的 502 错误无关。

1 个赞

看起来是数据库错误。因为我在另一个 Discourse 网站上尝试了相同的插件和配置,并且它能正常工作。我该如何解决这个问题?

尝试使用非 ASCII 用户名或组名(在本例中为中文)进行 discourse-auth-proxy 身份验证似乎会导致错误,因为 cookie 无法包含这些字符。这是我的修复方法:(免责声明:我并不真正熟悉 golang)

diff --git a/main.go b/main.go
index 1b1dc28..18f8c9e 100644
--- a/main.go
+++ b/main.go
@@ -154,7 +154,12 @@ func redirectIfNoCookie(handler http.Handler, r *http.Request, w http.ResponseWr
 		var username, groups string

 		if err == nil && cookie != nil {
-				username, groups, err = parseCookie(cookie.Value, config.CookieSecret)
+				var value string
+				value, err = url.QueryUnescape(cookie.Value)
+				if err != nil {
+						return
+				}
+				username, groups, err = parseCookie(value, config.CookieSecret)
 		}

 		if err == nil {
@@ -224,7 +229,7 @@ func redirectIfNoCookie(handler http.Handler, r *http.Request, w http.ResponseWr
 				cookieData := strings.Join([]string{username, strings.Join(groups, "|")}, ",")
 				http.SetCookie(w, &http.Cookie{
 						Name:     cookieName,
-						Value:    signCookie(cookieData, config.CookieSecret),
+						Value:    url.QueryEscape(signCookie(cookieData, config.CookieSecret)),
 						Expires:  expiration,
 						HttpOnly: true,
 						Path:     "/",
1 个赞

感谢您的反馈!您能否针对 GitHub 存储库创建一个 PR,以便我们将此更改合并到官方版本中?

4 个赞