Use Discourse as an identity provider (SSO, DiscourseConnect)

안녕하세요 :waving_hand:, 저는 SimpleSAMLphp 설치 환경에서 Discourse를 SSO 제공자로 사용할 수 있도록 이 SimpleSAMLphp 인증 모듈을 작성했습니다. 즉, SAML 또는 Shibboleth 인증을 지원하는 모든 서비스에서 Discourse를 SSO 제공자로 사용할 수 있어 매우 유용합니다.

생각하시는 점이 있다면 알려주세요. (코드에 대한 코멘트를 원하신다면 GitHub Issues를 사용할 수 있습니다.)

4개의 좋아요

정말 좋습니다! 모듈의 가시성을 높이고 싶다면, Customization > Extras 카테고리에 해당 모듈에 대한 주제를 생성할 수 있습니다. 이 카테고리는 Discourse 플러그인이 아닌 모든 Discourse 확장 및 통합의 디렉터리입니다,

3개의 좋아요

수정: 목업 이미지 추가


기존 웹사이트에 이 SSO를 구현하고 있는데, 사람들이 로그인 방식을 사용자에게 어떻게 "제시"하는지 확인하고 싶습니다.

예를 들어, www.example.com이라는 제 웹사이트가 상단 네비게이션에 로그인 버튼을 가지고 있다고 가정해 보겠습니다.

그 로그인 버튼이 즉시 제 discourse 로그인 인증 페이지로 사람들을 보내야 할까요? 아니면 먼저 정보 페이지나 모달을 표시하는 것이 더 좋을까요? 다음과 같은 형태로 말입니다:

무엇이 일어날 것인지 알려주지 않으면 사람들이 혼란스러워할까 하고 궁금합니다. :thinking:

누군가 사용자 경험이나 모범 사례를 공유해 주실 수 있을까요?

그리고 이 스레드의 첫 번째 게시글을 매우 자세히 작성해 주신 @techAPJ님께도 감사드립니다. 당신의 단계를 따라 ASP.NET 웹사이트에 이 기능을 처음부터 성공적으로 구축할 수 있었습니다. :+1:t2:

2개의 좋아요

OAuth에서처럼 변경되지 않고 반환되는 state 파라미터를 포함할 수 있나요? ASP.NET Core 인증 미들웨어는 CSRF 공격을 방지하기 위해 상관 ID(correlation ID)를 생성하는 데 의존하는데, 현재 이 값을 쉽게 포함할 방법이 없습니다.

콜백 URL의 쿼리 문자열에 매개변수를 추가한 후 이를 감싸는 것은 어떨까요?

예:

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

@jessicah 오늘 이 방법을 시도해 보았는데, 네, 잘 작동합니다.

        ' Return URL 생성
        Dim strReturnURL As String = "https://www.example.com/authtestRETURNURL.aspx?myownparametershere=surewhynot"

        ' 무작위 nonce 생성. 반환된 nonce 값으로 검증할 수 있도록 임시로 저장합니다
        Dim strNonce As String = Guid.NewGuid().ToString("N")

        ' nonce와 return 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. 로그인 요청의 출처에 따라 return_sso_url을 포함하는 페이로드가 변경되도록 discourse-auth-proxy 인스턴스를 하나만 사용하는 것.

두 방식 모두 작동할 수 있다고 생각하지만, 이 두 가지 접근 방식의 문제는 여전히 각 다른 사이트에 로그인해야 한다는 점입니다.
또한, 서로 다른 사이트에서 로그인할 때마다 Postgres에 저장된 데이터가 덮어쓰기 될 위험도 있습니다. 예를 들어: site1.com, site2.com
(Discourse 인증/PG 스키마의 세부 사항을 잘 모르기 때문에 정확한 내용은 알지 못합니다)

이상적으로는 한 번만 로그인하면 멀티사이트 그룹 내의 모든 사이트(site1.com, site2.com, site3.com 등)에 로그인된 상태가 되는 방식이 좋겠습니다.

Stackoverflow는 localSession 저장과 Iframe의 조합을 주요 수단으로 사용하여 이를 구현한다고 합니다. 기술 설명

하지만 저는 누군가가 Discourse를 SSO 제공자로 사용하여 멀티사이트 로그인에 어떤 접근 방식을 구현했는지 정말 알고 싶습니다.
접근 방식 1: discourse-auth-proxy의 여러 인스턴스 사용
접근 방식 2: 페이로드의 return_sso_url에 영향을 미치는 해킹된 discourse-auth-proxy 사용
접근 방식 3: #1 또는 #2를 구현하되, 한 번 로그인하면 site1.com에서 site2.com으로 이동할 때 다시 로그인하지 않아도 되는 방식

@sam 님에게 태그를 붙이는 이유는, Go로 작성된 discourse-auth-proxy 프로그램을 처음 작성하신 분이기 때문입니다.

안녕하세요.

Provider가 반환되는 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\&sso=foo&sig=bar
  2. http://example.com/foo/bar?wpLoginToken=123+\&sso=foo&sig=bar

문제는 반환 URL이 PHP의 urldecode 함수(MediaWiki 인증의 핵심 워크플로)에 의해 처리되는데, 이때 wpLoginToken 값이 123+\에서 123 \로 의도치 않게 변경된다는 점입니다.

원인을 찾은 것 같습니다:

결국 클라이언트와 서버가 인코딩/디코딩을 구현할 때 서로 다른 명세를 따르고 있었습니다.

Discourse 측에 변경을 요청하는 것이 아니라, 이 문제를 해결할 조언을 구하는 것입니다.

감사합니다.

수정:
+ 기호를 두 번 URL 인코딩하는 방식으로 해결했습니다.

3개의 좋아요

감사합니다!

지금 이걸 시도하고 있습니다. GitHub에서 두 개의 PR을 보냈는데, 하나는 문서 업데이트를 위한 것이고, 다른 하나는 제가 겪은 버그를 수정하기 위한 것입니다.

1개의 좋아요

설정 > 로그인에서 두 개의 DiscourseConnect provider 설정은 연속적으로 배치되어 있지 않고, DiscourseConnect 설정들 사이에 섞여 있습니다:

provider 설정과 비-provider 설정은 서로 반대되는 사용 사례(다른 서비스의 사용자를 Discourse로 관리하는 경우와 Discourse의 사용자를 다른 서비스로 관리하는 경우)를 위해 존재하므로, 이 설정들을 혼합하여 표시하면 잘못된 설정으로 이어질 수 있습니다. 두 provider 설정을 연속적으로 배치하고, 비-provider 설정 전체의 앞이나 뒤에 완전히 위치하도록 하면 혼란을 줄일 수 있습니다.

4개의 좋아요

@techAPJ AWS Cognito와 함께 사용할 수 있나요? AWS Amplify를 사용하여 Discourse 커뮤니티용 앱을 만들고, 내 앱이 Discourse를 통해 인증되기를 원합니다.

1개의 좋아요

너무 늦은 답변으로 죄송합니다. 말씀하신 대로 그렇게 섞여 있는 것이 혼란스럽네요. 이 PR에서 정리했습니다:

6개의 좋아요

@mdoggydog DiscourseSsoConsumer MediaWiki 확장 프로그램의 최근 업데이트에 감사드립니다. 우리 위키에서 사용자가 Discourse에서는 로그아웃하지 않았는데 위키에서는 로그아웃되는 문제에 대해 어떻게 해야 할지 고민하고 있었는데요, $wgPluggableAuth_EnableAutoLogin은 위키의 익명 접근을 차단하기 때문에 우리가 원했던 기능이 아니었습니다. 추가해 주신 $wgDiscourseSsoConsumer_AutoRelogin 설정이 바로 우리가 필요로 하던 것이었습니다.

1개의 좋아요

안녕하세요 :wave:!

원래 게시물에 있는 PHP 예제를 사용하려고 하는데, 데이터를 저장하는 방식이 이해가 되지 않습니다. 그냥 login과 nonce라는 키를 사용하여 SQL 데이터베이스에 값을 저장하고 있거든요. SQL을 사용해 nonce를 저장하려면 데이터베이스 구조가 정확히 어떻게 되어야 할까요?

도움이 될 수 있는 추가 정보로, 이걸 어떤 용도로 사용하려는지 말씀드리겠습니다. UUID에 연결된 SSO 링크를 생성하여 Discourse 사용자를 Minecraft 계정에 연결하려고 합니다. Discourse로 로그인에 성공하면, 해당 사용자의 UUID와 Discourse ID를 테이블에 저장할 예정입니다.

지금까지 PHP 예제는 작동하게 만들었지만, 제 사용 사례에 맞게 어떻게 수정해야 하는지 완전히 이해하지 못하고 있는 것 같습니다. 이상적으로는 GET 요청을 통해 링크를 생성하여 사용자에게 보내고, 이때 nonce에 이미 UUID가 연결되어 있기를 바랍니다.

이 게시물을 올려주셔서 감사합니다. 이 게시물이 없었으면 더 헤맬 뻔했습니다!

수정: nonce의 경우, 테이블에 nonce를 저장하고 그것으로 조회하는 것이 더 나은가요? nonce를 일치시켜야 한다는 것은 알고 있지만, 리다이렉트 URL을 통해 추가 정보를 전달할 수 있는 방법을 찾지 못했기 때문에(성공하지 못했습니다) nonce를 올바르게 참조하는 방법을 모르겠습니다.

2개의 좋아요

관리자가 SimpleSAMLphp를 통해 DiscourseConnect를 표준 SAML 프로토콜로 설정할 수 있도록 가이드를 작성했습니다.

1개의 좋아요

[details=“Link Detail”]
/session/sso_provider?sso=bm9uY2U9NDA3YTVlNjg1ZTEwMDlh...

[/details]502 bad gateway가 발생하는 이유를 정확히 알 수 없습니다.

제가 제공한 sso 파라미터의 값을 일부 편집했습니다. 비밀 키를 아는 사람이 아니라면 값을 복호화할 수 없겠지만, 그래도 전체 값을 제공하지 않는 것이 더 안전하다고 생각했습니다. 이 부분은 현재 발생하고 있는 502 오류와는 관련이 없습니다.

1개의 좋아요

데이터베이스 오류인 것 같습니다. 같은 플러그인과 설정을 다른 디스코urs 웹사이트에서 시도해 보았을 때 정상적으로 작동했기 때문입니다. 이 문제를 어떻게 해결할 수 있을까요?

비 ASCII 사용자 이름 또는 그룹 이름(제 경우 중국어)으로 discourse-auth-proxy에 인증을 시도하면 쿠키에 해당 문자를 포함할 수 없어 오류가 발생하는 것 같습니다. 제 수정 방법은 다음과 같습니다. (주의: 저는 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개의 좋아요