概要
Casdoorは現在、SAML IdPとして使用できます。 この時点まで、CasdoorはSAML 2.0の主要な機能をサポートしています。
SPでの設定
一般的に、SPはSingle Sign-On
、Issuer
、Public 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はSAMLResponse
をPOSTリクエストで送信します。 返信URLが空の場合、CasdoorはGETリクエストを使用します。 Reply URL
が空の場合、CasdoorがSPのReply URL
をどのように知るか疑問に思うかもしれません。 実際には、CasdoorはSAMLRequest
を解析してAssertionConsumerServiceURL
と呼ばれるURLを取得し、SAMLResponse
をAssertionConsumerServiceURL
に送信します。 Reply URL
はSAMLRequest
内のAssertionConsumerServiceURL
を上書きします。
返信URL:SAMLレスポンスを検証するACSのURLを入力します。
リダイレクトURL:一意の名前を入力します。 これはSPでは
Audience
またはEntity ID
と呼ばれることがあります。 SPで使用するのと同じRedirect URL
をここに入力してください。
ユーザープロファイル
正常にログインした後、Casdoorから返されるSAMLResponse
のユーザープロファイルには3つのフィールドがあります。 XMLの属性とCasdoorのユーザーの属性は以下のようにマッピングされます:
XML属性名 | ユーザーフィールド |
---|---|
メール | |
表示名 | 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/example
とhttp://localhost:6900/saml/acs/example
はapp-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のログインページが表示されます。
認証後、以下に示すような応答メッセージを受け取ります。