如何在不使用第三方控件的情况下执行SAML登录请求

本文关键字:情况下 执行 SAML 请求 登录 控件 第三方 | 更新日期: 2023-09-27 18:01:28

我需要做一个SAML登录到一个网站。我知道该站点正在使用ComponentPro Ultimate SAML来处理服务提供者端的SSO…我是身份提供者。如果我购买了控件,我有一个如何发布它的示例,但我的理解是SAML是一个开放标准,不应该需要特定的控件来执行断言。有没有人有一个如何在c#中格式化SAML请求的示例?如果服务提供者使用第三方库(如ComponentPro SAML库),我需要做任何特别的工作吗?

如何在不使用第三方控件的情况下执行SAML登录请求

Clayton,我们在编写的几个类的帮助下生成和签名SAML消息(如下)。这些课程是我们在线学习的高潮。断言是特定于我们的系统的,但我相信它将为您提供一个很好的起点。出于演示目的,假设私钥证书位于根目录下名为"Certificates"的文件夹中。在生产环境中,它们不会位于网站的根目录下。

------------------------------------------------------------
// Default.aspx
------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SSOClient
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            SSOLogin ssoLogin = new SSOLogin();
            ssoLogin.SAML = SSOLogin.SAML_TEMPLATE;
            ssoLogin.ServiceProviderUrl = "";
            ssoLogin.Certificate = new Certificate() { Path = Server.MapPath("/Certificates")  + "/" + "SSOClientCert.pfx", Password = "abcd1234" };
            ssoLogin.Issuer = "SSOClientCert";
            ssoLogin.NameID = "guest";
            ssoLogin.Logout = "http://www.google.com";
            ssoLogin.Controller = "Home";
            ssoLogin.TokenMethod = "SystemID";
            ssoLogin.CustomTokenForVerification = "46";
            ssoLogin.AutoSubmitMessage = false;
            ssoLogin.Post(Response);
            ssoLogin = null;
            /*SSOLogout ssoLogout = new SSOLogout();
            ssoLogout.SAML = SSOLogout.SAML_TEMPLATE;
            ssoLogout.ServiceProviderUrl = "";
            ssoLogout.Certificate = new Certificate() { Path = Server.MapPath("/Certificates") + "/" + "SSOClientCert.pfx", Password = "abcd1234" };
            ssoLogout.Issuer = "SSOClientCert";
            ssoLogout.NameID = "guest";
            ssoLogout.AutoSubmitMessage = false;
            ssoLogout.Post(Response);
            ssoLogout = null;*/
        }
    }
}
------------------------------------------------------------
// Classes
------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Web;
using System.Xml;
namespace SSOClient
{
#region Certificate Class
    public class Certificate
    {
        private string _Path = "";
        private string _Password = "";
        public string Path
        {
            get { return this._Path; }
            set { this._Path = value; }
        }
        public string Password
        {
            get { return this._Password; }
            set { this._Password = value; }
        }
    }
#endregion
#region SSOBase Class
    public abstract class SSOBase
    {
        public const string HIDDEN_INPUT_TEMPLATE = @"<input type=""hidden"" name=""{0}"" value=""{1}"" />";
        private Certificate _Certificate = null;
        private string _ServiceProviderUrl = "";
        private string _ConsumerServicePath = "";
        private string _RelayState = "";
        private string _MessageID = "";
        private string _IssueInstant = "";
        private string _Issuer = "";
        private string _NameID = "";
        private string _SigAlg = "";
        private string _Signature = "";
        private string _SAML = "";
        private string _SAMLSigned = "";
        private bool _AutoSubmitMessage = false;
        public Certificate Certificate
        {
            get { return this._Certificate; }
            set { this._Certificate = value; }
        }
        public string ServiceProviderUrl
        {
            get { return this._ServiceProviderUrl; }
            set { this._ServiceProviderUrl = value; }
        }
        public string ConsumerServicePath
        {
            get { return this._ConsumerServicePath; }
            set { this._ConsumerServicePath = value; }
        }
        public string RelayState
        {
            get { return this._RelayState; }
            set { this._RelayState = value; }
        }
        public string MessageID
        {
            get { return this._MessageID; }
            set { this._MessageID = value; }
        }
        public string IssueInstant
        {
            get { return this._IssueInstant; }
            set { this._IssueInstant = value; }
        }
        public string Issuer
        {
            get { return this._Issuer; }
            set { this._Issuer = value; }
        }
        public string NameID
        {
            get { return this._NameID; }
            set { this._NameID = value; }
        }
        public string SigAlg
        {
            get { return this._SigAlg; }
            set { this._SigAlg = value; }
        }
        public string Signature
        {
            get { return this._Signature; }
            set { this._Signature = value; }
        }
        public string SAML
        {
            get { return this._SAML; }
            set { this._SAML = value; }
        }
        public string SAMLSigned
        {
            get { return this._SAMLSigned; }
            set { this._SAMLSigned = value; }
        }
        public bool AutoSubmitMessage
        {
            get { return this._AutoSubmitMessage; }
            set { this._AutoSubmitMessage = value; }
        }
        public SSOBase()
        {
            this._Certificate = null;
            this._ServiceProviderUrl = "";
            this._ConsumerServicePath = "";
            this._RelayState = System.Guid.NewGuid().ToString();
            this._MessageID = String.Format("_{0}", System.Guid.NewGuid().ToString().Replace("-", "").ToUpper());
            this._IssueInstant = System.DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
            this._Issuer = "";
            this._NameID = "";
            this._SigAlg = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
            this._Signature = "";
            this._SAML = "";
            this._SAMLSigned = "";
            this._AutoSubmitMessage = false;
        }
        public XmlDocument Sign(X509Certificate2 x509Certificate, XmlDocument xmlDocument, string referenceId)
        {
            SignedXml signedXml = new SignedXml(xmlDocument);
            X509Certificate2Collection x509Certificate2Collection;
            try
            {
                x509Certificate2Collection = new X509Certificate2Collection();
                x509Certificate2Collection.Add(x509Certificate);
            }
            catch (Exception exception)
            {
                throw;
            }
            KeyInfo keyInfo;
            try
            {
                keyInfo = new KeyInfo();
                KeyInfoX509Data keyInfoX509Data = new KeyInfoX509Data();
                X509Certificate2Enumerator enumerator = x509Certificate2Collection.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    keyInfoX509Data.AddCertificate(enumerator.Current);
                }
                keyInfo.AddClause(keyInfoX509Data);
            }
            catch (Exception exception)
            {
                throw;
            }
            XmlElement xmlElement = null;
            try
            {
                signedXml.SigningKey = x509Certificate.PrivateKey;
                signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
                Reference reference = new Reference()
                {
                    Uri = string.Concat("#", referenceId)
                };
                reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
                XmlDsigExcC14NTransform xmlDsigExcC14NTransform = new XmlDsigExcC14NTransform();
                if (!string.IsNullOrEmpty("#default samlp saml ds xs xsi"))
                {
                    xmlDsigExcC14NTransform.InclusiveNamespacesPrefixList = "#default samlp saml ds xs xsi";
                }
                reference.AddTransform(xmlDsigExcC14NTransform);
                signedXml.AddReference(reference);
                signedXml.KeyInfo = keyInfo;
                signedXml.ComputeSignature();
                this._Signature = Convert.ToBase64String(signedXml.SignatureValue, Base64FormattingOptions.None);
                xmlElement = signedXml.GetXml();
                XmlNode root = xmlDocument.DocumentElement;
                XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDocument.NameTable);
                nsmgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion");
                XmlNode issuer = xmlDocument.SelectSingleNode("//saml:Issuer", nsmgr);
                root.InsertAfter(xmlElement, issuer);
                return xmlDocument;
            }
            catch (Exception exception)
            {
                return null;
            }
        }
    }
#endregion
#region SSOLogin Class
    public class SSOLogin : SSOBase
    {
        public const string SAML_TEMPLATE = @"<samlp:Response ID=""@ResponseID"" Version=""2.0"" IssueInstant=""@IssueInstant"" xmlns:samlp=""urn:oasis:names:tc:SAML:2.0:protocol""><saml:Issuer xmlns:saml=""urn:oasis:names:tc:SAML:2.0:assertion"">@Issuer</saml:Issuer><samlp:Status><samlp:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /></samlp:Status><saml:Assertion Version=""2.0"" ID=""@AssertionID"" IssueInstant=""@IssueInstant"" xmlns:saml=""urn:oasis:names:tc:SAML:2.0:assertion""><saml:Subject><saml:NameID>@NameID</saml:NameID></saml:Subject><saml:AuthnStatement AuthnInstant=""@AuthnInstant"" /><saml:AttributeStatement><saml:Attribute Name=""Logout"" NameFormat=""urn:oasis:names:tc:SAML:2.0:attrname-format:basic""><saml:AttributeValue>@Logout</saml:AttributeValue></saml:Attribute><saml:Attribute Name=""Controller"" NameFormat=""urn:oasis:names:tc:SAML:2.0:attrname-format:basic""><saml:AttributeValue>@Controller</saml:AttributeValue></saml:Attribute><saml:Attribute Name=""TokenMethod"" NameFormat=""urn:oasis:names:tc:SAML:2.0:attrname-format:basic""><saml:AttributeValue>@TokenMethod</saml:AttributeValue></saml:Attribute><saml:Attribute Name=""CustomTokenForVerification"" NameFormat=""urn:oasis:names:tc:SAML:2.0:attrname-format:basic""><saml:AttributeValue>@CustomTokenForVerification</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>";
        private string _AssertionID = "";
        private string _AuthnInstant = "";
        private string _Logout = "";
        private string _Controller = "";
        private string _TokenMethod = "";
        private string _CustomTokenForVerification = "";
        public string AssertionID
        {
            get { return this._AssertionID; }
            set { this._AssertionID = value; }
        }
        public string AuthnInstant
        {
            get { return this._AuthnInstant; }
            set { this._AuthnInstant = value; }
        }
        public string Logout
        {
            get { return this._Logout; }
            set { this._Logout = value; }
        }
        public string Controller
        {
            get { return this._Controller; }
            set { this._Controller = value; }
        }
        public string TokenMethod
        {
            get { return this._TokenMethod; }
            set { this._TokenMethod = value; }
        }
        public string CustomTokenForVerification
        {
            get { return this._CustomTokenForVerification; }
            set { this._CustomTokenForVerification = value; }
        }
        public SSOLogin()
        {
            this._AssertionID = String.Format("_{0}", System.Guid.NewGuid().ToString().Replace("-", "").ToUpper());
            this._AuthnInstant = System.DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
            this._Logout = "";
            this._Controller = "";
            this._TokenMethod = "";
            this._CustomTokenForVerification = "";
        }
        public void Post(HttpResponse response)
        {
            try
            {
                if (base.SAML == "")
                {
                    base.SAML = SAML_TEMPLATE;
                }
                base.SAML = base.SAML.Replace("@ResponseID", base.MessageID);
                base.SAML = base.SAML.Replace("@IssueInstant", base.IssueInstant);
                base.SAML = base.SAML.Replace("@Issuer", base.Issuer);
                base.SAML = base.SAML.Replace("@AssertionID", this._AssertionID);
                base.SAML = base.SAML.Replace("@AuthnInstant", this._AuthnInstant);
                base.SAML = base.SAML.Replace("@NameID", base.NameID);
                base.SAML = base.SAML.Replace("@Logout", this._Logout);
                base.SAML = base.SAML.Replace("@Controller", this._Controller);
                base.SAML = base.SAML.Replace("@TokenMethod", this._TokenMethod);
                base.SAML = base.SAML.Replace("@CustomTokenForVerification", this._CustomTokenForVerification);
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.LoadXml(base.SAML);
                XmlDocument xmlDocumentSigned = this.Sign(new X509Certificate2(base.Certificate.Path, base.Certificate.Password), xmlDocument, base.MessageID);
                byte[] signedByteArray = Encoding.UTF8.GetBytes(xmlDocumentSigned.InnerXml);
                base.SAMLSigned = Convert.ToBase64String(signedByteArray, Base64FormattingOptions.None);
                IDictionary<string, string> hiddenInputDic = new Dictionary<string, string>();
                hiddenInputDic.Add("SAMLResponse", base.SAMLSigned);
                hiddenInputDic.Add("RelayState", base.RelayState);
                StringBuilder hiddenInputs = new StringBuilder();
                foreach (string hiddenInputName in hiddenInputDic.Keys)
                {
                    string hiddenFieldValue = hiddenInputDic[hiddenInputName];
                    hiddenInputs.AppendFormat(SSOBase.HIDDEN_INPUT_TEMPLATE + Environment.NewLine,
                        hiddenInputName,
                        HttpUtility.HtmlEncode(hiddenFieldValue));
                }
                string samlForm = String.Format(@"
<form id=""samlForm"" action=""{0}"" method=""post"">
    {1}
    <input type=""submit"" name=""btnLogin"" value=""Login"" />
</form>",
                        String.Format("{0}{1}", this.ServiceProviderUrl, base.ConsumerServicePath),
                        hiddenInputs.ToString());
                string pageTemplate = @"
<!DOCTYPE html>
<html xmlns=""http://www.w3.org/1999/xhtml"">
    <body{0}>
        <h3>SAML SSO - Login Example</h3>
        <p>Please click the Login button to Login.</p>
        <div>
            {1}
        </div>
        <hr>
        <h3>SAML Form</h3>
        <div>
            <textarea cols=""100"" rows=""50"">
                {2} 
            </textarea>
        </div>
    </body>
</html>";
                HttpContext httpContext = HttpContext.Current;
                StringBuilder html = new StringBuilder();
                html.AppendFormat(pageTemplate,
                    base.AutoSubmitMessage ? @" onload=""document.getElementById('samlForm').submit();""" : "",
                    samlForm,
                    httpContext.Server.HtmlEncode(samlForm));
                StreamWriter outputStream = new StreamWriter(response.OutputStream);
                outputStream.Write(html.ToString());
                outputStream.Flush();
            }
            catch (Exception exception)
            {
                response.Write(exception.ToString());
            }
        }
    }
#endregion
#region SSOLogout Class
    public class SSOLogout : SSOBase
    {
        public const string SAML_TEMPLATE = @"<saml:LogoutRequest ID=""@RequestID"" Version=""2.0"" IssueInstant=""@IssueInstant"" xmlns:saml=""urn:oasis:names:tc:SAML:2.0:protocol""><saml:Issuer xmlns:saml=""urn:oasis:names:tc:SAML:2.0:assertion"">@Issuer</saml:Issuer><saml:NameID xmlns:saml=""urn:oasis:names:tc:SAML:2.0:assertion"">@NameID</saml:NameID></saml:LogoutRequest>";
        public SSOLogout()
        {
        }
        public void Post(HttpResponse response)
        {
            try
            {
                if (base.SAML == "")
                {
                    base.SAML = SAML_TEMPLATE;
                }
                base.SAML = base.SAML.Replace("@RequestID", base.MessageID);
                base.SAML = base.SAML.Replace("@IssueInstant", base.IssueInstant);
                base.SAML = base.SAML.Replace("@Issuer", base.Issuer);
                base.SAML = base.SAML.Replace("@NameID", base.NameID);
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.LoadXml(base.SAML);
                XmlDocument xmlDocumentSigned = this.Sign(new X509Certificate2(base.Certificate.Path, base.Certificate.Password), xmlDocument, base.MessageID);
                byte[] signedByteArray = Encoding.UTF8.GetBytes(xmlDocumentSigned.InnerXml);
                base.SAMLSigned = Convert.ToBase64String(signedByteArray, Base64FormattingOptions.None);
                IDictionary<string, string> hiddenInputDic = new Dictionary<string, string>();
                hiddenInputDic.Add("SAMLRequest", base.SAMLSigned);
                hiddenInputDic.Add("RelayState", base.RelayState);
                hiddenInputDic.Add("SigAlg", base.SigAlg);
                hiddenInputDic.Add("Signature", base.Signature);
                StringBuilder hiddenInputs = new StringBuilder();
                foreach (string hiddenInputName in hiddenInputDic.Keys)
                {
                    string hiddenFieldValue = hiddenInputDic[hiddenInputName];
                    hiddenInputs.AppendFormat(SSOBase.HIDDEN_INPUT_TEMPLATE + Environment.NewLine,
                        hiddenInputName,
                        HttpUtility.HtmlEncode(hiddenFieldValue));
                }
                string samlForm = String.Format(@"
<form id=""samlForm"" action=""{0}"" method=""post"">
    {1}
    <input type=""submit"" name=""btnLogout"" value=""Logout"" />
</form>", 
                    String.Format("{0}{1}", this.ServiceProviderUrl, base.ConsumerServicePath),
                    hiddenInputs.ToString());
                string pageTemplate = @"
<!DOCTYPE html>
<html xmlns=""http://www.w3.org/1999/xhtml"">
    <body{0}>
        <h3>SAML SSO - Logout Example</h3>
        <p>Please click the Logout button to Logout.</p>
        <div>
            {1}
        </div>
        <hr>
        <h3>SAML Form</h3>
        <div>
            <textarea cols=""100"" rows=""50"">
                {2} 
            </textarea>
        </div>
    </body>
</html>";
                HttpContext httpContext = HttpContext.Current;
                StringBuilder html = new StringBuilder();
                html.AppendFormat(pageTemplate,
                    base.AutoSubmitMessage ? @" onload=""document.getElementById('samlForm').submit();""" : "",
                    samlForm,
                    httpContext.Server.HtmlEncode(samlForm));
                StreamWriter outputStream = new StreamWriter(response.OutputStream);
                outputStream.Write(html.ToString());
                outputStream.Flush();
            }
            catch (Exception exception)
            {
                response.Write(exception.ToString());
            }
        }
    }
#endregion
}