2014-12-12 2 views
2

Мне было поручено добавить регистрацию через внешнюю службу (используя SAML 2.0) в приложение MVC (.Net 4.5), которое использует SimpleMembership. Честно говоря, я даже не знаю, с чего начать. Из того, что я нашел в Интернете, есть несколько проблем. Большинство материалов, которые я нашел, касались связи с провайдером идентификации SAML (часто написанным с нуля). Однако, прежде чем я смогу достичь этой цели, мне нужно убедиться, что я действительно смогу интегрировать ее с SimpleMembership, который мы используем.Вход в приложение SimpleMembership с использованием внешнего поставщика идентификации SAML

Я подозреваю, что для начала мне понадобится что-то вроде SAMLWebSecurity (сродни OAuthWebSecurity, который мы также используем). Я не нашел такой вещи в Интернете, которая заставляет меня думать, что ее не существует (хотя я бы не прочь здесь не ошибиться). Это заставляет меня поверить, что я должен сам написать это, но могу ли я сделать это, не имея необходимости писать собственный членский член?

* Я не уверен, что было бы правильным способом назвать этот статический класс.

ответ

2

После обсуждения с коллегой я думаю, что я понял ход действий. Оба OAuthWebSecurity и WebSecurity, как представляется, входят в состав SimpleMembership, поэтому то, что я написал в этом вопросе, указывает на то, что я хочу написать пользовательское членство или реверсный инженер SimpleMembership для копирования OAuthWebSecurity (что не похоже на забавную деятельность).

Лучше всего здесь захватить OAuthWebSecurity, написав пользовательский клиент (тот, который реализует интерфейс IAuthenticationClient). Обычно регистрируются различные клиенты OAuth с использованием встроенных методов OAuthWebSecurity (например, RegisterFacebookClient). Но также можно зарегистрировать этих клиентов, используя OAuthWebSecurity.RegisterClient, который принимает IAuthenticationClient. Таким образом, я должен быть в состоянии добавить этот логин SAML без написания пользовательского поставщика членства и продолжать использовать SimpleMembership.


Мне это удалось. К счастью, поставщик удостоверений не был чрезвычайно сложным, поэтому все, что мне нужно было сделать, это перенаправить на определенный адрес (мне даже не нужно было запрашивать утверждение). После успешного входа в систему IDP «перенаправляет» пользователя, использующего POST на мой сайт, с прикрепленным SAMLResponse с кодировкой base64. Поэтому все, что я должен был сделать, это разобрать и подтвердить ответ. Я поместил код для этого в свой пользовательский клиент (реализующий интерфейс IAuthenticationClient).

public class mySAMLClient : IAuthenticationClient 
{ 
    // I store the IDP certificate in App_Data 
    // This can by actually skipped. See VerifyAuthentication for more details 
    private static X509Certificate2 certificate = null; 
    private X509Certificate2 Certificate 
    { 
     get 
     { 
      if (certificate == null) 
      { 
       certificate = new X509Certificate2(Path.Combine(HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data"), "idp.cer")); 
      } 
      return certificate; 
     } 
    } 

    private string providerName; 
    public string ProviderName 
    { 
     get 
     { 
      return providerName; 
     } 
    } 

    public mySAMLClient() 
    { 
     // This probably should be provided as a parameter for the constructor, but in my case this is enough 
     providerName = "mySAML"; 
    } 

    public void RequestAuthentication(HttpContextBase context, Uri returnUrl) 
    { 
     // Normally you would need to request assertion here, but in my case redirecting to certain address was enough 
     context.Response.Redirect("IDP login address"); 
    } 

    public AuthenticationResult VerifyAuthentication(HttpContextBase context) 
    { 
     // For one reason or another I had to redirect my SAML callback (POST) to my OAUTH callback (GET) 
     // Since I needed to retain the POST data, I temporarily copied it to session 
     var response = context.Session["SAMLResponse"].ToString(); 
     context.Session.Remove("SAMLResponse"); 
     if (response == null) 
     { 
      throw new Exception("Missing SAML response!"); 
     } 
     // Decode the response 
     response = Encoding.UTF8.GetString(Convert.FromBase64String(response)); 


     // Parse the response 
     var assertion = new XmlDocument { PreserveWhitespace = true }; 
     assertion.LoadXml(response); 

     //Validating signature based on: http://stackoverflow.com/a/6139044 
     // adding namespaces 
     var ns = new XmlNamespaceManager(assertion.NameTable); 
     ns.AddNamespace("samlp", @"urn:oasis:names:tc:SAML:2.0:protocol"); 
     ns.AddNamespace("saml", @"urn:oasis:names:tc:SAML:2.0:assertion"); 
     ns.AddNamespace("ds", @"http://www.w3.org/2000/09/xmldsig#"); 

     // extracting necessary nodes 
     var responseNode = assertion.SelectSingleNode("/samlp:Response", ns); 
     var assertionNode = responseNode.SelectSingleNode("saml:Assertion", ns); 
     var signNode = responseNode.SelectSingleNode("ds:Signature", ns); 

     // loading the signature node 
     var signedXml = new SignedXml(assertion.DocumentElement); 
     signedXml.LoadXml(signNode as XmlElement); 


     // You can extract the certificate from the response, but then you would have to check if the issuer is correct 
     // Here we only check if the signature is valid. Since I have a copy of the certificate, I know who the issuer is 
     // So if the signature is valid I then it was sent from the right place (probably). 
     //var certificateNode = signNode.SelectSingleNode(".//ds:X509Certificate", ns); 
     //var Certificate = new X509Certificate2(System.Text.Encoding.UTF8.GetBytes(certificateNode.InnerText)); 

     // checking signature 
     bool isSigned = signedXml.CheckSignature(Certificate, true); 
     if (!isSigned) 
     { 
      throw new Exception("Certificate and signature mismatch!"); 
     } 

     // If you extracted the signature, you would check the issuer here 

     // Here is the validation of the response 
     // Some of this might be unnecessary in your case, or might not be enough (especially if you plan to use SAML for more than just SSO) 
     var statusNode = responseNode.SelectSingleNode("samlp:Status/samlp:StatusCode", ns); 
     if (statusNode.Attributes["Value"].Value != "urn:oasis:names:tc:SAML:2.0:status:Success") 
     { 
      throw new Exception("Incorrect status code!"); 
     } 
     var conditionsNode = assertionNode.SelectSingleNode("saml:Conditions", ns); 
     var audienceNode = conditionsNode.SelectSingleNode("//saml:Audience", ns); 
     if (audienceNode.InnerText != "Name of your app on the IDP") 
     { 
      throw new Exception("Incorrect audience!"); 
     } 
     var startDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotBefore"].Value, XmlDateTimeSerializationMode.Utc); 
     var endDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotOnOrAfter"].Value, XmlDateTimeSerializationMode.Utc); 
     if (DateTime.UtcNow < startDate || DateTime.UtcNow > endDate) 
     { 
      throw new Exception("Conditions are not met!"); 
     } 
     var fields = new Dictionary<string, string>(); 
     var userId = assertionNode.SelectSingleNode("//saml:NameID", ns).InnerText; 
     var userName = assertionNode.SelectSingleNode("//saml:Attribute[@Name=\"urn:oid:1.2.840.113549.1.9.1\"]/saml:AttributeValue", ns).InnerText; 
     // you can also extract some of the other fields in similar fashion 
     var result = new AuthenticationResult(true, ProviderName, userId, userName, fields); 
     return result; 


    } 
} 

Тогда я просто зарегистрировал мой клиент в App_Start \ AuthConfig.cs с помощью OAuthWebSecurity.RegisterClient, а затем я мог повторно использовать мой существующий код внешнего входа (который первоначально был сделан для OAuth). По разным причинам мой обратный вызов SAML был другим действием, чем мой обратный вызов OAUTH. Код для этой акции был более или менее это:

[AllowAnonymous] 
public ActionResult Saml(string returnUrl) 
{ 
    Session["SAMLResponse"] = Request.Form["SAMLResponse"]; 
    return Redirect(Url.Action("ExternalLoginCallback") + "?__provider__=mySAML"); 
} 

Кроме того OAuthWebSecurity.VerifyAuthentication не работал с моим клиентом слишком хорошо, так что я должен был условно запустить свою собственную проверку в OAuth обратного вызова.

AuthenticationResult result = null; 
if (Request.QueryString["__provider__"] == "mySAML") 
{ 
    result = new mySAMLClient().VerifyAuthentication(HttpContext); 
} 
else 
{ 
    // use OAuthWebSecurity.VerifyAuthentication 
} 

Это, наверное, все выглядит очень странно и может значительно отличаться в случае вашего IDP, но благодаря этому я смог повторно использовать большую часть существующего кода для обработки внешних счетов.

2

Я бы рекомендовал вам перейти на ASP.NET Identity и промежуточное программное обеспечение для проверки подлинности на основе OWIN. Затем вы можете использовать промежуточное программное обеспечение Kentor.AuthServices, которое работает с ASP.NET Identity (за исключением того, что XSRF-защитник должен быть прокомментирован до разрешения bug #127).

Вы также можете использовать классы SAML из Kentor.AuthServices, если вам нужно придерживаться SimpleMembership, так что вам не нужно реализовывать SAML с нуля.

Отказ от ответственности: Я автор Kentor.AuthServices, но поскольку это открытый исходный код, я не зарабатываю деньги на тех, кто его использует.

+0

Спасибо за предложение. Я уже смотрел на ваш код для этой цели (что не нужно писать все с нуля). – jahu

Смежные вопросы