.NETCORE 微软企业登录

1.常用类如下

Samlv1ClaimsTransform.cs

cs 复制代码
public static class Samlv1ClaimsTransform
{
    public static ClaimsPrincipal Transform(ClaimsPrincipal incomingPrincipal)
    {
        if (!incomingPrincipal.Identity.IsAuthenticated)
        {
            return incomingPrincipal;
        }

        return CreateClaimsPrincipal(incomingPrincipal);
    }

    private static ClaimsPrincipal CreateClaimsPrincipal(ClaimsPrincipal incomingPrincipal)
    {
        var claims = new List<Claim>();

        // All claims
        claims.AddRange(incomingPrincipal.Claims);

        // Or custom claims
        //claims.AddRange(GetSaml2LogoutClaims(incomingPrincipal));
        //claims.Add(new Claim(ClaimTypes.NameIdentifier, GetClaimValue(incomingPrincipal, ClaimTypes.NameIdentifier)));

        return new ClaimsPrincipal(new ClaimsIdentity(claims, incomingPrincipal.Identity.AuthenticationType, ClaimTypes.NameIdentifier, ClaimTypes.Role)
        {
            BootstrapContext = ((ClaimsIdentity)incomingPrincipal.Identity).BootstrapContext
        });
    }

    private static IEnumerable<Claim> GetSaml2LogoutClaims(ClaimsPrincipal principal)
    {
        yield return GetClaim(principal, Saml2ClaimTypes.NameId);
        yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat);
        yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex);
    }

    private static Claim GetClaim(ClaimsPrincipal principal, string claimType)
    {
        return ((ClaimsIdentity)principal.Identity).Claims.Where(c => c.Type == claimType).FirstOrDefault();
    }

    private static string GetClaimValue(ClaimsPrincipal principal, string claimType)
    {
        var claim = GetClaim(principal, claimType);
        return claim != null ? claim.Value : null;
    }
}

Samlv2AuthRequest.cs

cs 复制代码
public class Samlv2AuthRequest
{
    public string _id;
    private string _issue_instant;

    private string _issuer;
    private string _assertionConsumerServiceUrl;

    public enum AuthRequestFormat
    {
        Base64 = 1
    }

    public Samlv2AuthRequest(string issuer, string assertionConsumerServiceUrl)
    {
        _id = "_" + Guid.NewGuid().ToString();
        _issue_instant = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", System.Globalization.CultureInfo.InvariantCulture);

        _issuer = issuer;
        _assertionConsumerServiceUrl = assertionConsumerServiceUrl;
    }

    public string GetRequest(AuthRequestFormat format)
    {
        using (StringWriter sw = new StringWriter())
        {
            XmlWriterSettings xws = new XmlWriterSettings();
            xws.OmitXmlDeclaration = true;

            using (XmlWriter xw = XmlWriter.Create(sw, xws))
            {
                xw.WriteStartElement("samlp", "AuthnRequest", "urn:oasis:names:tc:SAML:2.0:protocol");
                xw.WriteAttributeString("ID", _id);
                xw.WriteAttributeString("Version", "2.0");
                xw.WriteAttributeString("IssueInstant", _issue_instant);
                xw.WriteAttributeString("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
                xw.WriteAttributeString("AssertionConsumerServiceURL", _assertionConsumerServiceUrl);

                xw.WriteStartElement("saml", "Issuer", "urn:oasis:names:tc:SAML:2.0:assertion");
                xw.WriteString(_issuer);
                xw.WriteEndElement();

                xw.WriteStartElement("samlp", "NameIDPolicy", "urn:oasis:names:tc:SAML:2.0:protocol");
                xw.WriteAttributeString("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
                xw.WriteAttributeString("AllowCreate", "true");
                xw.WriteEndElement();

                /*xw.WriteStartElement("samlp", "RequestedAuthnContext", "urn:oasis:names:tc:SAML:2.0:protocol");
				xw.WriteAttributeString("Comparison", "exact");
				xw.WriteStartElement("saml", "AuthnContextClassRef", "urn:oasis:names:tc:SAML:2.0:assertion");
				xw.WriteString("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
				xw.WriteEndElement();
				xw.WriteEndElement();*/

                xw.WriteEndElement();
            }

            if (format == AuthRequestFormat.Base64)
            {
                //byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(sw.ToString());
                //return System.Convert.ToBase64String(toEncodeAsBytes);

                //https://stackoverflow.com/questions/25120025/acs75005-the-request-is-not-a-valid-saml2-protocol-message-is-showing-always%3C/a%3E
                var memoryStream = new MemoryStream();
                var writer = new StreamWriter(new DeflateStream(memoryStream, CompressionMode.Compress, true), new UTF8Encoding(false));
                writer.Write(sw.ToString());
                writer.Close();
                string result = Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length, Base64FormattingOptions.None);
                return result;
            }

            return null;
        }
    }

    //returns the URL you should redirect your users to (i.e. your SAML-provider login URL with the Base64-ed request in the querystring
    public string GetRedirectUrl(string samlEndpoint, string relayState = null)
    {
        var queryStringSeparator = samlEndpoint.Contains("?") ? "&" : "?";

        var url = samlEndpoint + queryStringSeparator + "SAMLRequest=" + Uri.EscapeDataString(GetRequest(AuthRequestFormat.Base64));

        if (!string.IsNullOrEmpty(relayState))
        {
            url += "&RelayState=" + Uri.EscapeDataString(relayState);
        }

        return url;
    }
}

Samlv2Response.cs

cs 复制代码
 public class Samlv2Response
 {
     private static byte[] StringToByteArray(string st)
     {
         byte[] bytes = new byte[st.Length];
         for (int i = 0; i < st.Length; i++)
         {
             bytes[i] = (byte)st[i];
         }
         return bytes;
     }

     protected XmlDocument _xmlDoc;
     protected readonly X509Certificate2 _certificate;
     protected XmlNamespaceManager _xmlNameSpaceManager; //we need this one to run our XPath queries on the SAML XML

     public string Xml { get { return _xmlDoc.OuterXml; } }

     public Samlv2Response(string certificateStr, string responseString)
         : this(StringToByteArray(certificateStr), responseString) { }

     public Samlv2Response(byte[] certificateBytes, string responseString) : this(certificateBytes)
     {
         LoadXmlFromBase64(responseString);
     }

     public Samlv2Response(string certificateStr) : this(StringToByteArray(certificateStr)) { }

     public Samlv2Response(byte[] certificateBytes)
     {
         _certificate = new X509Certificate2(certificateBytes);
     }

     public void LoadXml(string xml)
     {
         _xmlDoc = new XmlDocument();
         _xmlDoc.PreserveWhitespace = true;
         _xmlDoc.XmlResolver = null;
         _xmlDoc.LoadXml(xml);

         _xmlNameSpaceManager = GetNamespaceManager(); //lets construct a "manager" for XPath queries
     }

     public void LoadXmlFromBase64(string response)
     {
         UTF8Encoding enc = new UTF8Encoding();
         LoadXml(enc.GetString(Convert.FromBase64String(response)));
     }

     public bool IsValid()
     {
         XmlNodeList nodeList = _xmlDoc.SelectNodes("//ds:Signature", _xmlNameSpaceManager);

         SignedXml signedXml = new SignedXml(_xmlDoc);

         if (nodeList.Count == 0) return false;

         signedXml.LoadXml((XmlElement)nodeList[0]);
         return ValidateSignatureReference(signedXml) && signedXml.CheckSignature(_certificate, true) && !IsExpired();
     }

     public string GetSamlXmlResponse()
     {
         return _xmlDoc.InnerXml;
     }

     //an XML signature can "cover" not the whole document, but only a part of it
     //.NET's built in "CheckSignature" does not cover this case, it will validate to true.
     //We should check the signature reference, so it "references" the id of the root document element! If not - it's a hack
     private bool ValidateSignatureReference(SignedXml signedXml)
     {
         if (signedXml.SignedInfo.References.Count != 1) //no ref at all
             return false;

         var reference = (Reference)signedXml.SignedInfo.References[0];
         var id = reference.Uri.Substring(1);

         var idElement = signedXml.GetIdElement(_xmlDoc, id);

         if (idElement == _xmlDoc.DocumentElement)
             return true;
         else //sometimes its not the "root" doc-element that is being signed, but the "assertion" element
         {
             var assertionNode = _xmlDoc.SelectSingleNode("/samlp:Response/saml:Assertion", _xmlNameSpaceManager) as XmlElement;
             if (assertionNode != idElement)
                 return false;
         }

         return true;
     }

     private bool IsExpired()
     {
         DateTime expirationDate = DateTime.MaxValue;
         XmlNode node = _xmlDoc.SelectSingleNode("/samlp:Response/saml:Assertion[1]/saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData", _xmlNameSpaceManager);
         if (node != null && node.Attributes["NotOnOrAfter"] != null)
         {
             DateTime.TryParse(node.Attributes["NotOnOrAfter"].Value, out expirationDate);
         }
         return DateTime.UtcNow > expirationDate.ToUniversalTime();
     }

     public string GetNameID()
     {
         XmlNode node = _xmlDoc.SelectSingleNode("/samlp:Response/saml:Assertion[1]/saml:Subject/saml:NameID", _xmlNameSpaceManager);
         return node.InnerText;
     }
     public string GetUserID()
     {
         return GetCustomAttribute("http://schemas.microsoft.com/identity/claims/objectidentifier");
     }
     public string GetRoles()
     {
         return GetCustomAttribute("User.roles")
               ?? GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/roles") //some providers (for example Azure AD) put last name into an attribute named "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
               ?? GetCustomAttribute("roles"); //some providers put last name into an attribute named "mail"
     }
     public virtual string GetUpn()
     {
         return GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn");
     }

     public virtual string GetEmail()
     {
         return GetCustomAttribute("User.email")
             ?? GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress") //some providers (for example Azure AD) put last name into an attribute named "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
             ?? GetCustomAttribute("mail"); //some providers put last name into an attribute named "mail"
     }

     public virtual string GetFirstName()
     {
         return GetCustomAttribute("first_name")
             ?? GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname") //some providers (for example Azure AD) put last name into an attribute named "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
             ?? GetCustomAttribute("User.FirstName")
             ?? GetCustomAttribute("givenName"); //some providers put last name into an attribute named "givenName"
     }

     public virtual string GetLastName()
     {
         return GetCustomAttribute("last_name")
             ?? GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname") //some providers (for example Azure AD) put last name into an attribute named "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
             ?? GetCustomAttribute("User.LastName")
             ?? GetCustomAttribute("sn"); //some providers put last name into an attribute named "sn"
     }

     public virtual string GetDepartment()
     {
         return GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/department")
             ?? GetCustomAttribute("department");
     }

     public virtual string GetPhone()
     {
         return GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone")
             ?? GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/telephonenumber");
     }

     public virtual string GetCompany()
     {
         return GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/companyname")
             ?? GetCustomAttribute("organization")
             ?? GetCustomAttribute("User.CompanyName");
     }

     public virtual string GetLocation()
     {
         return GetCustomAttribute("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/location")
             ?? GetCustomAttribute("physicalDeliveryOfficeName");
     }

     public string GetCustomAttribute(string attr)
     {
         XmlNode node = _xmlDoc.SelectSingleNode("/samlp:Response/saml:Assertion[1]/saml:AttributeStatement/saml:Attribute[@Name='" + attr + "']/saml:AttributeValue", _xmlNameSpaceManager);
         return node == null ? null : node.InnerText;
     }

     //returns namespace manager, we need one b/c MS says so... Otherwise XPath doesnt work in an XML doc with namespaces
     //see https://stackoverflow.com/questions/7178111/why-is-xmlnamespacemanager-necessary
     private XmlNamespaceManager GetNamespaceManager()
     {
         XmlNamespaceManager manager = new XmlNamespaceManager(_xmlDoc.NameTable);
         manager.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
         manager.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion");
         manager.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol");

         return manager;
     }
 }

2.控制器调用

cs 复制代码
//根据企业码查看当前的企业码是否存在,然后并且返回跳转数据
[Route("sso/ssologin")]
[HttpGet]
public async Task<ActionResult<Result>> SsoLogin(string CorporateCode)
{
    if (!string.IsNullOrEmpty(CorporateCode))
    {
        //查询企业码是否存在
        var result = await ------**.GetSpIdp(CorporateCode, "", "", "");
        if (result.Rows.Count == 0)
        {
            return ApiResultHelper.renderError("CorporateCode is null ");
        }
        if (result.Rows[0]["isena**"].ToString() == "0")
        {
            return ApiResultHelper.renderError("CorporateCode is null ");
        }
        var samlEndpoint = result.Rows[0]["idpsso**"].ToString();
        var sp_consumer_url = result.Rows[0]["spconsum**"].ToString();
        var request = new Samlv2AuthRequest(
           "****", //TODO: put your app's "entity ID" here urn:auth0:YOUR_TENANT:YOUR_CONNECTION_NAME
           sp_consumer_url //TODO: put Assertion Consumer URL (where the provider should redirect users after authenticating)
           );
        //redirect the user to the SAML provider
        return ApiResultHelper.renderSuccess(request.GetRedirectUrl(samlEndpoint), "success!");//, "https://www.corriere.it/"
    }
    return ApiResultHelper.renderError("CorporateCode is null ");
}
cs 复制代码
[Route("**/**/**/{param1}/{param2}")]
[HttpPost]
//ASP.NET MVC action method... But you can easily modify the code for Web-forms etc.
public async Task<ActionResult<Result>> Samlv2Consumer(string param1, string param2)
{
    var url = "";
    var result = await ------**.GetSpIdp("", "", param2, param1);
    if (result.Rows.Count == 0)
    {
        return ApiResultHelper.renderError("request is fail");
    }
    // 1. TODO: specify the certificate that your SAML provider gave you
    string samlCertificate = result.Rows[0]["idp_certificate"].ToString();
    // string samlCertificate = ByFingerpritntGetBase64("**");

    // 2. Let's read the data - SAML providers usually POST it into the "SAMLResponse" var
    var samlResponse = new Samlv2Response(samlCertificate, Request.Form["SAMLResponse"]);

    //for log purpose
    var samlTextResponse = samlResponse.GetSamlXmlResponse();
    Dictionary<string, string> dic = new Dictionary<string, string>();
    // 3. We're done!
    if (samlResponse.IsValid())
    {
        //WOOHOO!!! user is logged in
        //Some more optional stuff for you
        //let's extract username/firstname etc
        string username, email, firstname, lastname, userid;
        try
        {
            userid = samlResponse.GetUserID();
            username = samlResponse.GetNameID();
            email = samlResponse.GetEmail();
            firstname = samlResponse.GetFirstName();
            lastname = samlResponse.GetLastName();
           
             /*删除部分代码*/
            url = Appsettings.GetNode("**") + "error";
            return Redirect(url);
        }
        catch (Exception ex)
        {
            //insert error handling code
            //no, really, please do
            return ApiResultHelper.renderError(ex.Message);
        }

        //user has been authenticated, put your code here, like set a cookie or something...
        //or call FormsAuthentication.SetAuthCookie()
        //or call context.SignInAsync() in ASP.NET Core
        //or do something else
    }
    return ApiResultHelper.renderError(false);
}
相关推荐
一点媛艺16 分钟前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风20 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程2 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man3 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang