概述
现在可以使用Casdoor作为SAML IdP。 到目前为止,Casdoor已经支持了SAML 2.0的主要功能。
在SP中的配置
一般来说,SP需要三个必填字段:Single Sign-On
,Issuer
和Public Certificate
。 大多数SP可以通过上传XML元数据文件或XML元数据URL来自动完成这些字段。
Casdoor中的SAML端点的元数据是<Endpoint of casdoor>/api/saml/metadata?application=admin/<application name>
。 假设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支持哪些类型的请求。 您需要根据您的SP支持的SAMLResponse
类型在Casdoor中配置应用程序。
如果您填写了Reply URL
,Casdoor将通过POST请求发送SAMLResponse
。 如果回复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
。 确保您在此处填写的Redirect URL
与您的SP中的一致。
用户资料
成功登录后,Casdoor返回的SAMLResponse
中的用户资料有三个字段。 XML中的属性和Casdoor中的用户属性的映射如下:
XML属性名称 | 用户字段 |
---|---|
电子邮件 | 电子邮件 |
显示名称 | 显示名称 |
名称 | 名称 |
有关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/example
和http://localhost:6900/saml/acs/example
,应该添加到app-built-in
中的重定向URLs。
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的登录页面将被显示。
身份验证后,您将收到如下所示的响应消息。