메인 콘텐츠로 건너뛰기

개요

Casdoor는 이제 SAML IdP로 사용될 수 있습니다. 지금까지, Casdoor는 SAML 2.0의 주요 기능을 지원하였습니다.

SP에서의 설정

일반적으로, SP는 세 가지 필수 필드를 요구합니다: Single Sign-On, Issuer, 그리고 Public Certificate. 대부분의 SP는 XML 메타데이터 파일을 업로드하거나 XML 메타데이터 URL을 자동완성하여 이러한 필드를 얻을 수 있습니다.

Casdoor의 SAML 엔드포인트 메타데이터는 <Casdoor의 엔드포인트>/api/saml/metadata?application=admin/<어플리케이션 이름>입니다. Casdoor의 엔드포인트가 https://door.casdoor.com이고, app-built-in이라는 어플리케이션이 포함되어 있다고 가정합시다. XML 메타데이터 엔드포인트는 다음과 같습니다:

https://door.casdoor.com/api/saml/metadata?application=admin/app-built-in

어플리케이션 편집 페이지에서 메타데이터를 찾을 수도 있습니다. 버튼을 클릭하여 URL을 복사하고 브라우저에 붙여넣어 XML 메타데이터를 다운로드합니다.

메타데이터

Casdoor IdP에서의 설정

Casdoor는 GET과 POST SAMLResponse를 모두 지원합니다. Casdoor가 SAMLResponse를 SP에게 보낼 때, Casdoor는 SP가 어떤 유형의 요청을 지원하는지 알아야 합니다. 당신은 Casdoor에서 애플리케이션을 구성해야 합니다. 이는 귀하의 SP가 지원하는 SAMLResponse 유형에 기반합니다.

정보

Reply URL을 입력하면, Casdoor는 POST 요청으로 SAMLResponse를 보냅니다. 응답 URL이 비어 있으면, Casdoor는 GET 요청을 사용합니다. Casdoor가 Reply URL이 비어 있을 경우 SP의 Reply URL을 어떻게 알 수 있는지 궁금할 수 있습니다. 실제로, Casdoor는 SAMLRequest를 파싱하여 AssertionConsumerServiceURL이라는 URL을 얻고, SAMLResponse를 가진 요청을 AssertionConsumerServiceURL로 보낼 수 있습니다. Reply URLSAMLRequestAssertionConsumerServiceURL을 덮어씁니다.

  • Reply URL: SAML 응답을 검증하는 ACS의 URL을 입력하세요.

    응답 URL

  • Redirect URL: 고유한 이름을 입력하세요. 이것은 SP에서 Audience 또는 Entity ID로 불릴 수 있습니다. 당신의 SP에서와 같은 Redirect URL을 여기에 입력하세요.

    Entity ID

사용자 프로필

성공적으로 로그인 한 후, Casdoor에서 반환된 SAMLResponse의 사용자 프로필에는 세 개의 필드가 있습니다. XML의 속성과 Casdoor의 사용자 속성은 다음과 같이 매핑됩니다:

XML 속성 이름사용자 필드
이메일이메일
DisplayNamedisplayName
이름이름

SAML 및 그 다양한 버전에 대한 자세한 정보는 https://en.wikipedia.org/wiki/SAML_2.0를 참조하세요.

예시

gosaml2는 etree와 goxmldsig를 기반으로 한 서비스 제공자를 위한 SAML 2.0 구현입니다. 이는 XML 디지털 서명의 순수 Go 구현입니다. 우리는 아래와 같이 Casdoor에서 SAML 2.0을 테스트하기 위해 이 라이브러리를 사용합니다.

http://localhost:7001/을 통해 Casdoor에 접근할 수 있다고 가정하고, 귀하의 Casdoor에는 app-built-in이라는 애플리케이션이 있습니다. 이는 built-in이라는 조직에 속해 있습니다. URL들, http://localhost:6900/acs/examplehttp://localhost:6900/saml/acs/example,은 app-built-in의 Redirect URL에 추가되어야 합니다.

import (
"crypto/x509"
"fmt"
"net/http"

"io/ioutil"

"encoding/base64"
"encoding/xml"

saml2 "github.com/russellhaering/gosaml2"
"github.com/russellhaering/gosaml2/types"
dsig "github.com/russellhaering/goxmldsig"
)

func main() {
res, err := http.Get("http://localhost:7001/api/saml/metadata?application=admin/app-built-in")
if err != nil {
panic(err)
}

rawMetadata, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}

metadata := &types.EntityDescriptor{}
err = xml.Unmarshal(rawMetadata, metadata)
if err != nil {
panic(err)
}

certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}

for _, kd := range metadata.IDPSSODescriptor.KeyDescriptors {
for idx, xcert := range kd.KeyInfo.X509Data.X509Certificates {
if xcert.Data == "" {
panic(fmt.Errorf("metadata certificate(%d) must not be empty", idx))
}
certData, err := base64.StdEncoding.DecodeString(xcert.Data)
if err != nil {
panic(err)
}

idpCert, err := x509.ParseCertificate(certData)
if err != nil {
panic(err)
}

certStore.Roots = append(certStore.Roots, idpCert)
}
}

randomKeyStore := dsig.RandomKeyStoreForTest()

sp := &saml2.SAMLServiceProvider{
IdentityProviderSSOURL: metadata.IDPSSODescriptor.SingleSignOnServices[0].Location,
IdentityProviderIssuer: metadata.EntityID,
ServiceProviderIssuer: "http://localhost:6900/acs/example",
AssertionConsumerServiceURL: "http://localhost:6900/v1/_saml_callback",
SignAuthnRequests: true,
AudienceURI: "http://localhost:6900/saml/acs/example",
IDPCertificateStore: &certStore,
SPKeyStore: randomKeyStore,
}

http.HandleFunc("/v1/_saml_callback", func(rw http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
return
}
samlReponse := req.URL.Query().Get("SAMLResponse")
assertionInfo, err := sp.RetrieveAssertionInfo(samlReponse)
if err != nil {
fmt.Println(err)
rw.WriteHeader(http.StatusForbidden)
return
}
fmt.Println(assertionInfo)
if assertionInfo.WarningInfo.InvalidTime {
fmt.Println("here12:", assertionInfo.WarningInfo.InvalidTime)
rw.WriteHeader(http.StatusForbidden)
return
}

if assertionInfo.WarningInfo.NotInAudience {
fmt.Println(assertionInfo)
fmt.Println("here13:", assertionInfo.WarningInfo.NotInAudience)
rw.WriteHeader(http.StatusForbidden)
return
}

fmt.Fprintf(rw, "NameID: %s\n", assertionInfo.NameID)

fmt.Fprintf(rw, "Assertions:\n")

for key, val := range assertionInfo.Values {
fmt.Fprintf(rw, " %s: %+v\n", key, val)
}
fmt.Println(assertionInfo.Values.Get("FirstName"))
fmt.Fprintf(rw, "\n")

fmt.Fprintf(rw, "Warnings:\n")
fmt.Fprintf(rw, "%+v\n", assertionInfo.WarningInfo)
})

println("Visit this URL To Authenticate:")
authURL, err := sp.BuildAuthURL("")
if err != nil {
panic(err)
}

println(authURL)

println("Supply:")
fmt.Printf(" SP ACS URL : %s\n", sp.AssertionConsumerServiceURL)

err = http.ListenAndServe(":6900", nil)
if err != nil {
panic(err)
}
}

위의 코드를 실행하면, 콘솔에 다음과 같은 메시지가 표시됩니다.

Visit this URL To Authenticate:
http://localhost:7001/login/saml/authorize/admin/app-built-in?SAMLRequest=lFVbk6K8Fv0rFvNo2QR...
Supply:
SP ACS URL : http://localhost:6900/v1/_saml_callback

URL을 클릭하여 인증하면, Casdoor의 로그인 페이지가 표시됩니다.

로그인

인증 후, 아래와 같이 응답 메시지를 받게 됩니다.

응답