创建 DiscourseConnect 登录链接

:bookmark: 本文档介绍如何在 DiscourseConnect 提供方网站上创建链接,这些链接可以将用户登录到 Discourse 并将他们重定向到特定的 Discourse URL。

:person_raising_hand: 所需用户级别:管理员

使用 DiscourseConnect 的网站可以在其 DiscourseConnect 提供方网站上添加链接,这些链接可以登录 Discourse 用户并将他们重定向到特定的 Discourse URL。这是通过创建指向 /session/sso 路由的链接来实现的,这些链接设置了 return_path 参数,指向您希望用户最终到达的 Discourse 页面路径。

链接的 href 属性应采用以下格式,将您希望用户最终到达的路径替换为 <相对路径>

https://forum.example.com/session/sso?return_path=<相对路径>

一个将用户登录并将其重定向到 Discourse 网站主页的示例锚点标签:

<a href="https://forum.example.com/session/sso?return_path=/">社区</a>

一个将用户登录并将其重定向到“热门话题”页面的示例锚点标签:

<a href="https://forum.example.com/session/sso?return_path=/top">热门话题</a>

return_path 在 Discourse 上的存储方式

Discourse 将 return_path 参数的值存储在服务器会话中,其键是用户访问 /session/sso 路由时生成的 SSO 随机数(nonce)。在 DiscourseConnect 身份验证过程结束时,Discourse 会使用该随机数查找 return_path 并将用户重定向到该路径。

使已认证用户体验无缝化

当用户访问 Discourse 的 /session/sso 路由时,他们将被重定向到由 discourse connect url 站点设置定义的 URL。然后,DiscourseConnect 提供方将以与用户点击 Discourse 登录 按钮时相同的方式处理身份验证过程。

为了让已经登录身份验证提供方网站的用户获得无缝体验,身份验证提供方的 DiscourseConnect 代码需要检查用户是否已登录。如果用户未登录,则引导他们完成身份验证提供方的登录流程。如果用户已登录,则跳过身份验证提供方网站上的登录流程。

这是一个带注释的示例,使用了 WP Discourse 插件 中的代码。它演示了如何区别对待已认证用户和未认证用户:

public function sso_parse_request( $wp ) {
    // 检查插件选项中是否启用了单点登录 (SSO)
    if ( empty( $this->options['enable-sso'] ) ) {
        return null;
    }

    // 在继续 SSO 之前处理任何登出请求
    $this->handle_logout_request();

    // 检查查询变量中是否存在 'sso' 和 'sig' 参数
    if ( array_key_exists( 'sso', $wp->query_vars ) && array_key_exists( 'sig', $wp->query_vars ) ) {
        // 清理 'sso' 有效负载和签名,以确保它们安全可用
        $payload = sanitize_text_field( $wp->query_vars['sso'] );
        $sig     = sanitize_text_field( $wp->query_vars['sig'] );

        // 如果用户未登录 WordPress,则重定向到登录页面
        // 这确保了没有活动会话的用户首先被提示登录 WordPress
        if ( ! is_user_logged_in() ) {
            // 构建一个 URL,用于在登录后重定向回来
            $redirect = add_query_arg( $payload, $sig );
            // 生成带有重定向参数的 WordPress 登录 URL
            $login    = wp_login_url( esc_url_raw( $redirect ) );

            // 在重定向到登录页面之前触发一个动作(用于记录或自定义操作是可选的)
            do_action( 'wpdc_sso_before_login_redirect', $redirect, $login );

            // 重定向到 WordPress 登录页面
            return $this->redirect_to( $login );
        } else {
            // 如果用户已在 WordPress 中通过身份验证,则绕过登录流程
            // 并继续验证 SSO 有效负载和签名。
            $sso_secret = $this->options['sso-secret'];
            $sso        = new SSO( $sso_secret );
            
            // 使用 SSO 密钥验证有效负载和签名
            if ( ! ( $sso->validate( $payload, $sig ) ) ) {
                // 处理无效的 SSO 请求
                return $this->handle_error( 'parse_request.invalid_sso' );
            }

            // 获取当前已登录的 WordPress 用户
            $current_user = wp_get_current_user();
            // 使用已登录用户的数据准备 SSO 参数
            $params       = $this->get_sso_params( $current_user );

            try {
                // 从有效负载生成一个随机数并构建 SSO 登录字符串
                $params['nonce'] = $sso->get_nonce( $payload );
                $q               = $sso->build_login_string( $params );
            } catch ( \Exception $e ) {
                // 如果 SSO 参数生成出现问题,则处理异常
                return $this->handle_error( 'parse_request.invalid_sso_params', array( 'message' => esc_html( $e->getMessage() ) ) );
            }

            // 在将用户重定向以进行 SSO 登录之前触发一个动作(对记录很有用)
            do_action( 'wpdc_sso_provider_before_sso_redirect', $current_user->ID, $current_user );

            // 如果启用了详细的 SSO 日志记录,则记录 SSO 成功
            if ( ! empty( $this->options['verbose-sso-logs'] ) ) {
                $this->logger->info( 'parse_request.success', array( 'user_id' => $current_user->ID ) );
            }

            // 将已认证用户重定向到带有 SSO 登录字符串的 DiscourseConnect 登录 URL
            return $this->redirect_to( $this->options['url'] . '/session/sso_login?' . $q );
        }
    }

    // 如果请求中未找到 SSO 参数,则返回 null
    return null;
}

将返回路径设置为非 Discourse URL

Discourse 允许您登录用户并将其重定向到非 Discourse URL。请注意,要使此功能正常工作,您需要在 discourse connect allowed redirect domains 站点设置中添加该域名。默认情况下,此设置为空白,这会阻止重定向到非 Discourse URL。您也可以使用 * 作为通配符来允许所有域名。如果启用此设置,请确保在 return_path 参数中使用绝对 URL,以指向您希望用户转到的任何非 Discourse URL。

21 个赞

我正在使用此方法,但每次都需要通过 sso 登录。

我们如何实现无缝登录,即如果我已经登录到 discourse,就不需要再次访问 sso 页面进行登录?

1 个赞

除非有什么变化,否则链接就是这样工作的。例如,如果您已登录 Discourse 并单击 SSO 提供商网站上指向 https://forum.example.com/session/sso?return_path=/t/some-slug/23 的链接,您应该会被无缝重定向到 /t/some-slug/23,而无需先访问登录页面。

1 个赞

我已经更新到最新的 discourse 版本,SSO 对我来说是这样工作的:

正如你所见,我已经登录了,但是当我输入像 https://forum.example.com/session/sso?return_path=/t/some-slug/23 这样的 URL 时,我会被再次重定向到 SSO 登录页面。

我认为发生的情况是,当您访问类似 https://forum.example.com/session/sso?return_path=/t/some-slug/23 这样的路由时,Discourse 会将您重定向到 discourse connect url,无论您是否已登录 Discourse。这发生在这里:

然后,SSO 提供商网站需要处理已登录网站的用户的情况。WP Discourse 插件是这样处理的:

该代码(else 语句后面的部分)处理已登录 WordPress 的用户的情况。他们将被重定向回由 return_path 查询参数提供的 URL。因此,从用户的角度来看,他们被直接带到 return path URL,但实际发生的是他们被重定向到 SSO 提供商网站,然后再返回 Discourse。

我认为您网站上的问题在于您的 SSO 代码没有处理用户已登录网站的情况。

我现在没有设置来测试这一点。我可能错误地解读了代码。在查看代码之前,我认为 Discourse 端会运行一个检查,以查看用户是否已登录 Discourse,但这似乎不是它的工作方式。

3 个赞

非常感谢您的解释。

是的,这是我们将在 SSO 中修复的一个问题。

但是

我认为在 Discourse 端进行此检查会带来更好的用户体验。

1 个赞

我对 OP 进行了编辑,添加了关于该流程如何工作以及如何处理已在 SSO 提供商网站上进行身份验证的用户的信息。这是一个经常被问到的问题。

也许这个主题的一个不太详细的版本可以整合到 Setup DiscourseConnect - Official Single-Sign-On for Discourse (sso) - #482 中。该主题没有提到 return_path 查询参数。