メインコンテンツにスキップ

概要

Casdoorは現在、SAML IdPとして使用できます。 この時点まで、CasdoorはSAML 2.0の主要な機能をサポートしています。

SPでの設定

一般的に、SPはSingle Sign-OnIssuerPublic Certificateの3つの必須フィールドを要求します。 ほとんどの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はSAMLResponseのGETとPOSTの両方をサポートしています。 CasdoorがSPにSAMLResponseを送信する際、SPがサポートするリクエストの種類をCasdoorが知る必要があります。 SPがサポートするSAMLResponseタイプに基づいてCasdoorでアプリケーションを設定する必要があります。

情報

Reply URLを入力すると、CasdoorはSAMLResponsePOSTリクエストで送信します。 返信URLが空の場合、CasdoorはGETリクエストを使用します。 Reply URLが空の場合、CasdoorがSPのReply URLをどのように知るか疑問に思うかもしれません。 実際には、CasdoorはSAMLRequestを解析してAssertionConsumerServiceURLと呼ばれるURLを取得し、SAMLResponseAssertionConsumerServiceURLに送信します。 Reply URLSAMLRequest内のAssertionConsumerServiceURLを上書きします。

  • 返信URL:SAMLレスポンスを検証するACSのURLを入力します。

    返信URL

  • リダイレクトURL:一意の名前を入力します。 これはSPではAudienceまたはEntity IDと呼ばれることがあります。 SPで使用するのと同じRedirect URLをここに入力してください。

    エンティティID

ユーザープロファイル

正常にログインした後、Casdoorから返されるSAMLResponseのユーザープロファイルには3つのフィールドがあります。 XMLの属性とCasdoorのユーザーの属性は以下のようにマッピングされます:

XML属性名ユーザーフィールド
メールemail
表示名displayName
名前name

SAMLとその異なるバージョンについての詳細は、https://en.wikipedia.org/wiki/SAML_2.0を参照してください。

gosaml2は、etreeとgoxmldsigに基づいたサービスプロバイダー向けのSAML 2.0実装で、XMLデジタル署名の純粋なGo実装です。 以下に示すように、このライブラリを使用してCasdoorでのSAML 2.0をテストします。

Casdoorにhttp://localhost:7001/を通じてアクセスでき、built-inという組織に属するapp-built-inというアプリケーションが含まれているとします。 URLhttp://localhost:6900/acs/examplehttp://localhost:6900/saml/acs/exampleapp-built-inのリダイレクト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のログインページが表示されます。

ログイン

認証後、以下に示すような応答メッセージを受け取ります。

応答