DiscourseConnect — это ключевая функция Discourse, позволяющая настроить «Единый вход (SSO)» для полной делегации всей регистрации и входа пользователей из Discourse на другой сайт. Доступно для наших клиентов с тарифами Pro, Business и Enterprise.
(Февраль 2021) «Discourse SSO» теперь называется «DiscourseConnect». Если вы используете старую версию Discourse, настройки ниже будут называться
sso_..., а неdiscourse_connect_...
Проблема
Многие сайты, желающие интегрироваться с сайтом Discourse, хотят оставить всю регистрацию пользователей на отдельном сайте. В такой конфигурации все операции входа должны быть делегированы этому другому сайту.
Что делать, если я хочу использовать SSO вместе с существующей аутентификацией?
Идея DiscourseConnect заключается в замене аутентификации в Discourse. Если вы хотите добавить нового провайдера, обратитесь к существующим плагинам, например: Discourse VK Authentication (vkontakte)
Включение DiscourseConnect
Чтобы включить DiscourseConnect, необходимо заполнить три настройки:
enable_discourse_connect: должно быть включено, глобальный переключатель
discourse_connect_url: внешний URL, на который будут перенаправлены пользователи при попытке входа
discourse_connect_secret: секретная строка, используемая для хеширования полезной нагрузки SSO. Гарантирует подлинность полезной нагрузки.
После установки enable_discourse_connect в значение true:
- Нажатие на кнопку входа или аватар перенаправит вас на
/session/sso, который, в свою очередь, перенаправит пользователей наdiscourse_connect_urlс подписанной полезной нагрузкой. - Пользователям не будет разрешено «изменять пароль». Это поле удаляется из профиля пользователя.
- Пользователи больше не смогут использовать аутентификацию Discourse (имя пользователя/пароль, Google и т. д.).
Что делать, если вы случайно отметили эту опцию?
См.: Log back in as admin after locking yourself out with read-only mode or an invalid SSO configuration
Реализация DiscourseConnect на вашем сайте
Discourse использует адреса электронной почты для сопоставления внешних пользователей с пользователями Discourse и предполагает, что внешние адреса электронной почты защищены. ЕСЛИ ВЫ НЕ ПРОВЕРЯЕТЕ АДРЕСА ЭЛЕКТРОННОЙ ПОЧТЫ ПЕРЕД ИХ ОТПРАВКОЙ В DISCOURSE, ВАШ САЙТ БУДЕТ ЧРЕЗВЫЧАЙНО УЯЗВИМ!
В качестве альтернативы, если вы настаиваете на отправке непроверенных адресов электронной почты, УБЕДИТЕСЬ, что установлено значение require_activation=true. Это заставит Discourse проверить все адреса электронной почты. МЫ ВСЁ ЕЩЁ НАСТОЙЧИВО РЕКОМЕНДУЕМ НЕ ДЕЛАТЬ ЭТОГО, поэтому, если вы продолжите с включённой этой настройкой, вы берёте на себя значительный риск.
Discourse перенаправит клиентов на discourse_connect_url с подписанной полезной нагрузкой: (предположим, discourse_connect_url — это https://somesite.com/sso)
Вы получите входящий трафик следующего вида:
https://somesite.com/sso?sso=PAYLOAD&sig=SIG
Полезная нагрузка — это строка, закодированная в Base64, содержащая nonce и return_sso_url. Полезная нагрузка всегда является допустимой строкой запроса.
Например, если nonce равен ABCD, то raw_payload будет:
nonce=ABCD&return_sso_url=https%3A%2F%2Fdiscourse_site%2Fsession%2Fsso_login, эта исходная полезная нагрузка закодирована в Base64.
Вызываемая конечная точка должна:
- Проверить подпись: убедиться, что HMAC-SHA256 от
PAYLOAD(с использованиемdiscourse_connect_secretв качестве ключа) равенsig(sigбудет закодирован в шестнадцатеричном формате). - Выполнить необходимую аутентификацию.
- Создать новую полезную нагрузку, закодированную в URL, содержащую как минимум nonce, email и external_id. Вы также можете предоставить дополнительные данные; вот список всех ключей, которые понимает Discourse:
- nonce должен быть скопирован из входной полезной нагрузки.
- email должен быть проверенным адресом электронной почты. Если адрес электронной почты не был проверен, установите require_activation в значение «true».
- external_id — это любая строка, уникальная для пользователя, которая никогда не изменится, даже если изменятся его адрес электронной почты, имя и т. д. Рекомендуемое значение — номер строки «id» в вашей базе данных.
- username станет именем пользователя в Discourse, если пользователь новый или установлено значение
SiteSetting.auth_overrides_username. - name станет полным именем в Discourse, если пользователь новый или установлено значение
SiteSetting.auth_overrides_name. - avatar_url будет загружен и установлен как аватар пользователя, если пользователь новый или установлено значение
SiteSetting.discourse_connect_overrides_avatar. - avatar_force_update — это булево поле. Если установлено в true, оно заставит Discourse обновить аватар пользователя, независимо от того, изменился ли
avatar_urlили нет. - bio станет содержимым биографии пользователя, если пользователь новый, его биография пуста или установлено значение
SiteSetting.discourse_connect_overrides_bio. - title установит должность пользователя.
- website установит веб-сайт пользователя в его профиле.
- location установит местоположение пользователя в его профиле.
- profile_background_url будет загружен и установлен как фон профиля пользователя, если пользователь новый или установлено значение
SiteSetting.discourse_connect_overrides_profile_background. - card_background_url будет загружен и установлен как фон карточки пользователя, если пользователь новый или установлено значение
SiteSetting.discourse_connect_overrides_card_background. - locale установит локаль пользователя, если пользователь новый и включено значение
SiteSetting.allow_user_locale. - locale_force_update — это булево поле. Если установлено в true вместе с locale, оно заставит обновить локаль для существующих пользователей (требуется
SiteSetting.allow_user_locale). - Дополнительные булевы поля («true» или «false»): admin, moderator, suppress_welcome_message, logout.
- Закодировать полезную нагрузку в Base64.
- Рассчитать хеш HMAC-SHA256 от полезной нагрузки, используя
discourse_connect_secretв качестве ключа и полезную нагрузку, закодированную в Base64, в качестве текста. - Перенаправить обратно на
return_sso_urlс параметрами запросаssoиsig(http://discourse_site/session/sso_login?sso=payload&sig=sig).
Discourse проверит, что nonce действителен, и если да, то сразу же истечёт его срок действия, чтобы его нельзя было использовать повторно. Затем он попытается:
- Войти пользователя, найдя уже связанный external_id в модели
SingleSignOnRecord. - Войти пользователя, используя предоставленный адрес электронной почты (обновляя external_id) (если require_activation = true).
- Создать новую учётную запись для пользователя, предоставив (email, username, name), обновляя external_id.
Вопросы безопасности
Nonce (одноразовый токен) автоматически истекает через 30 минут. Это означает, что как только пользователь будет перенаправлен на ваш сайт, у него есть 30 минут для входа или создания новой учётной записи.
Протокол защищён от атак повторного воспроизведения, так как nonce может быть использован только один раз. Nonce привязан к текущей сессии браузера для защиты от CSRF-атак.
Указание членства в группах
Если указана опция discourse connect overrides groups, Discourse будет учитывать список групп, разделённый запятыми, переданный в groups.
Помимо groups, вы также можете указать членство в группах в своей полезной нагрузке SSO, используя атрибуты add_groups и remove_groups, независимо от опции discourse connect overrides groups.
add_groups — это список названий групп, разделённый запятыми, в которых мы гарантируем, что пользователь является участником.
remove_groups — это список названий групп, разделённый запятыми, в которых мы гарантируем, что пользователь не является участником.
Референсная реализация
Discourse содержит референсную реализацию класса SSO:
Тривиальная реализация может выглядеть так:
class DiscourseSsoController < ApplicationController
def sso
secret = "MY_SECRET_STRING"
sso = DiscourseApi::SingleSignOn.parse(request.query_string, secret)
sso.email = "user@email.com"
sso.name = "Bill Hicks"
sso.username = "bill@hicks.com"
sso.external_id = "123" # уникальный идентификатор для каждого пользователя вашего приложения
sso.sso_secret = secret
redirect_to sso.to_url("http://l.discourse/session/sso_login")
end
end
Переход к и от единого входа.
Пока параметр require_activation не установлен в true в полезной нагрузке запроса, система доверяет адресам электронной почты, предоставленным конечной точкой единого входа. Это означает, что если у вас ранее была существующая учётная запись в Discourse с отключённым DiscourseConnect, DiscourseConnect просто повторно использует её и избегает создания новой учётной записи.
Если вы когда-либо отключите DiscourseConnect, пользователи смогут сбросить пароли и получить доступ к своим учётным записям.
Пример из реальной жизни:
При следующих настройках:
Домен Discourse: http://discuss.example.com
URL DiscourseConnect: http://www.example.com/discourse/sso
Секрет DiscourseConnect: d836444a9e4084d5b224a60c208dce14
Проверка электронной почты: Нет (добавьте require_activation=true в полезную нагрузку)
Попытка пользователя войти в систему
-
Генерируется nonce:
cb68251eefb5211e58c00ff1395f0c0b -
Генерируется исходная полезная нагрузка:
nonce=cb68251eefb5211e58c00ff1395f0c0b -
Полезная нагрузка кодируется в Base64:
bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI= -
Полезная нагрузка кодируется в URL:
bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D -
Генерируется HMAC-SHA256 для полезной нагрузки, закодированной в Base64:
1ce1494f94484b6f6a092be9b15ccc1cdafb1f8460a3838fbb0e0883c4390471
Наконец, браузер перенаправляется на:
http://www.example.com/discourse/sso?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D&sig=1ce1494f94484b6f6a092be9b15ccc1cdafb1f8460a3838fbb0e0883c4390471
На другом конце
- Полезная нагрузка проверяется с помощью HMAC-SHA256; если sig не совпадает, процесс прерывается.
- Путём обратного выполнения вышеуказанных шагов извлекается nonce.
Пользователь входит в систему:
name: sam
external_id: hello123
email: test@test.com
username: samsam
require_activation: true
Генерируется неподписанная полезная нагрузка:
nonce=cb68251eefb5211e58c00ff1395f0c0b&name=sam&username=samsam&email=test%40test.com&external_id=hello123&require_activation=true
порядок не имеет значения, значения кодируются в URL
Полезная нагрузка кодируется в Base64:
bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ==
Полезная нагрузка кодируется в URL:
bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ%3D%3D
Полезная нагрузка, закодированная в Base64, подписывается:
3d7e5ac755a87ae3ccf90272644ed2207984db03cf020377c8b92ff51be3abc3
Браузер перенаправляется на:
http://discuss.example.com/session/sso_login?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ%3D%3D&sig=3d7e5ac755a87ae3ccf90272644ed2207984db03cf020377c8b92ff51be3abc3
Синхронизация записей DiscourseConnect
Вы можете использовать POST-конечную точку администратора /admin/users/sync_sso для синхронизации записи DiscourseConnect, передав ей ту же запись, которую вы бы передали конечной точке DiscourseConnect; nonce не имеет значения.
Если вы вызываете admin/users/sync_sso с другого сайта, вам нужно включить в заголовки запроса действительный api_key администратора и действительное api_username. Подробнее о том, как структурировать запрос, см. на Sync DiscourseConnect user data with the sync_sso route.
Очистка записей DiscourseConnect
Если значения external_id от вашего провайдера DiscourseConnect изменились (возможно, вы изменили алгоритм генерации, возможно, это другая конечная точка), вы можете безопасно удалить все существующие записи, используя консоль Rails:
SingleSignOnRecord.destroy_all
Выход пользователей из системы
Вы можете использовать POST-конечную точку администратора /admin/users/{USER_ID}/log_out для выхода любого пользователя из системы при необходимости.
Чтобы настроить конечную точку, на которую перенаправляет Discourse при выходе, найдите настройку logout redirect. Если здесь не установлен URL, вы будете перенаправлены обратно на URL, настроенный в discourse connect url.
Поиск пользователей по external_id
Данные профиля пользователя можно получить с помощью конечной точки /users/by-external/{EXTERNAL_ID}.json. Это вернёт полезную нагрузку JSON, содержащую информацию о пользователе, включая user_id, который можно использовать с конечной точкой log_out.
Существующие реализации
-
Гем
discourse_apiможно использовать для SSO. Посмотрите код SSO в его каталоге примеров, чтобы увидеть базовую реализацию. -
Наш плагин для WordPress упрощает настройку SSO между WordPress и Discourse. Подробные сведения о его настройке находятся на вкладке SSO на странице параметров плагина.
Будущая работа
- Мы хотели бы собрать больше референсных реализаций SSO для других платформ. Если у вас есть такая, пожалуйста, опубликуйте её в категории Dev / SSO.
Расширенные функции
- Вы можете передавать пользовательские поля пользователей, добавляя префикс
customк имени поля. Например,custom.user_field_1можно использовать для установки значенияUserCustomFieldс именемuser_field_1. - Вы можете передать
avatar_urlдля переопределения аватара пользователя (должно быть включеноSiteSetting.discourse_connect_overrides_avatar). Аватары кэшируются, поэтому передайтеavatar_force_update=true, чтобы принудительно обновить их, если URL одинаков. В настоящее время вы не можете передать пустой URL для отключения аватара пользователя. - По умолчанию всем новым пользователям, созданным через SSO, отправляется приветственное сообщение. Если вы хотите отключить это, передайте
suppress_welcome_message=true. - Чтобы настроить ваш экземпляр Discourse в качестве провайдера Discourse Connect, см.: Использование DiscourseConnect в качестве провайдера идентификации.
Отладка вашего провайдера DiscourseConnect
Чтобы помочь в отладке DiscourseConnect, вы можете включить настройку сайта verbose_discourse_connect_logging. При включении этой настройки подробная диагностика будет отображаться в YOURSITE.com/logs. Обязательно
поле warnings в нижней части YOURSITE.com/logs.
Мы будем записывать предупреждение в логи с полным дампом полезной нагрузки SSO:
-
Каждый раз, когда инициируется процесс DiscourseConnect, мы будем записывать предупреждение в лог с полным дампом полезной нагрузки DiscourseConnect.
-
Каждый раз, когда пользователю не удаётся завершить DiscourseConnect (из-за истечения nonce или блокировки IP).
Нужно автоматизировать регистрацию пользователей? См. Auto-provisioning user accounts when SSO is enabled

