SharePoint 2010: Proveedor de Autenticación Personalizado (Custom Membership Provider)

Hola a todos.

Hoy, tras mis breves vacaciones voy a exponer una cuestión bastante común sobre la autenticación de usuarios en nuestros sitios de SharePoint.

 

Escenario

En ocasiones se nos hace necesario autenticar usuarios contra almacenes externos a SharePoint o, simplemente realizar la autenticación de una forma diferente a la habitual. En este caso, haré una exposición de autenticación contra una fuente de datos externa.

 

Solución

Para empezar, lo primero es que nuestra aplicación web debe tener activada la autenticación basada en claims y la autenticación basada en formularios (FBA).

 

auth1

auth2

 

Lo siguiente es crear una clase MyMembershipProvider que herede de System.Web.Security.MembershipProvider.

public class MyMembership : System.Web.Security.MembershipProvider

{

    ...

}

 

Hecho esto, nos aparecerá subrayado “System.Web.Security.MembershipProvider” debido a que es necesario implementar sus métodos ya que se trata de una clase abstracta. Esta primera parte está descrita en el siguiente artículo de MSDN http://msdn.microsoft.com/en-us/library/f1kyba5e.aspx.

Una vez los hayamos implementado (Click con el botón derecho del ratón –> Implementar clase abstracta), habrá que determinar qué métodos son necesarios para el correcto funcionamiento de nuestra autenticación y, escribir el código necesario. Como mínimo, se debe tener en cuenta los siguientes métodos:

  • FindUsersByEmail
  • FindUsersByName
  • GetUser(string, bool)
  • GetUser(object, bool)
  • GetUserNameByEmail
  • ValidateUser

 

Para el ejemplo, mi clase queda de la siguiente forma.

public class MyMembership : System.Web.Security.MembershipProvider

{

    private string pApplicationName = "";

 

    public override string ApplicationName

    {

        get { return pApplicationName; }

        set { pApplicationName = value; }

    }

 

    private int pMaxInvalidPasswordAttempts = 0;

 

    public override int MaxInvalidPasswordAttempts

    {

        get { return pMaxInvalidPasswordAttempts; }

    }

 

    private int pMinRequiredNonAlphanumericCharacters = 0;

 

    public override int MinRequiredNonAlphanumericCharacters

    {

        get { return pMinRequiredNonAlphanumericCharacters; }

    }

 

    private int pMinRequiredPasswordLength = 0;

 

    public override int MinRequiredPasswordLength

    {

        get { return pMinRequiredPasswordLength; }

    }

 

    private int pPasswordAttemptWindow = 0;

 

    public override int PasswordAttemptWindow

    {

        get { return pPasswordAttemptWindow; }

    }

 

    private MembershipPasswordFormat pPasswordFormat = MembershipPasswordFormat.Clear;

 

    public override System.Web.Security.MembershipPasswordFormat PasswordFormat

    {

        get { return pPasswordFormat; }

    }

 

    private string pPasswordStrengthRegularExpression = "";

 

    public override string PasswordStrengthRegularExpression

    {

        get { return pPasswordStrengthRegularExpression; }

    }

 

    private bool pRequiresQuestionAndAnswer = false;

 

    public override bool RequiresQuestionAndAnswer

    {

        get { return pRequiresQuestionAndAnswer; }

    }

 

    private bool pRequiresUniqueEmail = false;

 

    public override bool RequiresUniqueEmail

    {

        get { return pRequiresUniqueEmail; }

    }

 

    private bool pEnablePasswordReset = false;

 

    public override bool EnablePasswordReset

    {

        get { return pEnablePasswordReset; }

    }

 

    private bool pEnablePasswordRetrieval = false;

 

    public override bool EnablePasswordRetrieval

    {

        get { return pEnablePasswordRetrieval; }

    }

 

    public override bool ChangePassword(string username, string oldPassword, string newPassword)

    {

        throw new NotImplementedException();

    }

 

    public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)

    {

        throw new NotImplementedException();

    }

 

    public override System.Web.Security.MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out System.Web.Security.MembershipCreateStatus status)

    {

        throw new NotImplementedException();

    }

 

    public override bool DeleteUser(string username, bool deleteAllRelatedData)

    {

        throw new NotImplementedException();

    }

 

    public override System.Web.Security.MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)

    {

        // Valido que el email introducido contenga la @

        if (!emailToMatch.Contains("@"))

        {

            totalRecords = 0;

            return null;

        }

 

        // Mediante el uso de un servicio WCF obtengo los usuarios que coincidan con el email especificado.

        using (var service = new WCFUser.ServiceUserClient())

        {

            var users = service.FindByEmail(emailToMatch);

            MembershipUserCollection mUsers = new MembershipUserCollection();

 

            foreach (WCFUser.User user in users)

            {

                MembershipUser mUser = new MembershipUser("MySecurityMembershipProvider",

                                                         user.Name,

                                                         user.Username,

                                                         user.Email,

                                                         "",

                                                         "",

                                                         true,

                                                         false,

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime());

                mUsers.Add(mUser);

            }

            totalRecords = mUsers.Count;

            return mUsers;

        }

    }

 

    public override System.Web.Security.MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)

    {

       // Mediante el uso de un servicio WCF obtengo los usuarios que coincidan con el username especificado.

       using (var service = new WCFUser.ServiceUserClient())

        {

            var users = service.FindByName(usernameToMatch);

            MembershipUserCollection mUsers = new MembershipUserCollection();

 

            foreach (WCFUser.User user in users)

            {

                MembershipUser mUser = new MembershipUser("MySecurityMembershipProvider",

                                                         user.Name,

                                                         user.Username,

                                                         user.Email,

                                                         "",

                                                         "",

                                                         true,

                                                         false,

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime(),

                                                         DateTime.Now.ToLocalTime());

                mUsers.Add(mUser);

            }

            totalRecords = mUsers.Count;

            return mUsers;

        }

    }

 

    public override System.Web.Security.MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)

    {

        throw new NotImplementedException();

    }

 

    public override int GetNumberOfUsersOnline()

    {

        throw new NotImplementedException();

    }

 

    public override string GetPassword(string username, string answer)

    {

        throw new NotImplementedException();

    }

 

    public override System.Web.Security.MembershipUser GetUser(string username, bool userIsOnline)

    {

        // Mediante el uso de un servicio WCF obtengo el usuario.

        using (var service = new WCFUser.ServiceUserClient())

        {

            WCFUser.User user = service.GetUser(username);

            MembershipUser mUser = new MembershipUser("MySecurityMembershipProvider",

                                                     user.Name,

                                                     user.Username,

                                                     user.Email,

                                                     "",

                                                     "",

                                                     true,

                                                     false,

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime());

            return mUser;

        }

    }

 

    public override System.Web.Security.MembershipUser GetUser(object providerUserKey, bool userIsOnline)

    {

        // Mediante el uso de un servicio WCF obtengo el usuario.

        using (var service = new WCFUser.ServiceUserClient())

        {

            WCFUser.User user = service.GetUser(providerUserKey.ToString());

            MembershipUser mUser = new MembershipUser("MySecurityMembershipProvider",

                                                     user.Name,

                                                     user.Username,

                                                     user.Email,

                                                     "",

                                                     "",

                                                     true,

                                                     false,

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime(),

                                                     DateTime.Now.ToLocalTime());

            return mUser;

        }

    }

 

    public override string GetUserNameByEmail(string email)

    {

        // Mediante el uso de un servicio WCF obtengo el usuario.

        using (var service = new WCFUser.ServiceUserClient())

        {

            return service.GetUsernameByEmail(email);

        }

    }

 

    public override string ResetPassword(string username, string answer)

    {

        throw new NotImplementedException();

    }

 

    public override bool UnlockUser(string userName)

    {

        throw new NotImplementedException();

    }

 

    public override void UpdateUser(System.Web.Security.MembershipUser user)

    {

        throw new NotImplementedException();

    }

 

    public override bool ValidateUser(string username, string password)

    {

        // Mediante el uso de un servicio WCF realizo la validación del usuario

        using (var service = new WCFUser.ServiceUserClient())

        {

            return service.Validate(username, password);

        }

    }

}

 

Finalmente, hay que crear el Proveedor de Roles (RoleProvider) de la misma forma que el MembershipProvider. Para ello creamos la clase MyRoleProvider que hereda de System.Web.Security.RoleProvider. Al ser también una clase abstracta, habrá que implementar sus métodos.

En este caso, como mínimo debemos tener en cuenta los métodos

  • GetRolesForUser
  • RoleExists

 

Para el ejemplo, mi clase queda de la siguiente forma.

public class MyRoleProvider : System.Web.Security.RoleProvider

 {

 

     public override void AddUsersToRoles(string[] usernames, string[] roleNames)

     {

         throw new NotImplementedException();

     }

 

     private string pApplicationName = "";

 

     public override string ApplicationName

     {

         get { return pApplicationName; }

         set { pApplicationName = value; }

     }

 

     public override void CreateRole(string roleName)

     {

         throw new NotImplementedException();

     }

 

     public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)

     {

         throw new NotImplementedException();

     }

 

     public override string[] FindUsersInRole(string roleName, string usernameToMatch)

     {

         throw new NotImplementedException();

     }

 

     public override string[] GetAllRoles()

     {

         throw new NotImplementedException();

     }

 

     public override string[] GetRolesForUser(string username)

     {

        // Hago uso de un servicio para obtener los roles de un usuario

         using (var service = new WCFSecurity.ServiceRole())

         {

             var roles = service.GetRolesForUser(username);

             var result = new List<string>();

             foreach (WCFSecurity.Resort role in roles)

                 result.Add(role.Code);

             return result.ToArray();

         }

     }

 

     public override string[] GetUsersInRole(string roleName)

     {

         throw new NotImplementedException();

     }

 

     public override bool IsUserInRole(string username, string roleName)

     {

         throw new NotImplementedException();

     }

 

     public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)

     {

         throw new NotImplementedException();

     }

 

     public override bool RoleExists(string roleName)

     {

        // Hago uso de un servivio para comprobar si existe el rol

         using (var service = new WCFSecurity.ServiceRole())

         {

             return (service.RoleExists(roleName));

         }

     }

 }

 

Llegado este punto ya tenemos preparado nuestro proveedor de autenticación personalizado (custom membership provider) y sólo tendremos que integrarlo en SharePoint 2010 haciendo uso, para ello, de los web.config.

NOTA: No es nada recomendable tocar los archivos web.config de SharePoint (ni en general) directamente.

 

Para ello, debemos seguir los siguientes pasos:

  • Ubicar la dll de nuestro custom membership en el GAC. Para esto, debemos copiar la dll resultante de la compilación de nuestro proyecto en “C:Windowsassembly”.
    • Es muy importante, ver las propiedades de la dll tras copiarla en el GAC para quedarnos con la clave pública que se genera.
  • Editar el web.config de la aplicación web de SharePoint 2010 en la que queremos activar nuestra autenticación y añadir las siguientes referencias. Al añadirlas, se podrá asignar permisos a los usuarios o roles del custom membership provider desde la administración del sitio web
<membership …>

    <providers>

        <add name="MyMembership" 

             type="ProjectName.Namespace.MyMembership, DllName, 

             Version=1.0.0.0,Culture=neutral, PublicKeyToken=GAC_TOKEN" />

    </providers>

</membership>

 

Dentro de <roleManager…> <providers…></providers></roleManager>

<roleManager … enabled="true">

    <providers>

        ...

        <add name="MyRoleManager" 

             type="ProjectName.Namespace.MyRoleProvider, DllName, Version=1.0.0.0,                    

             Culture=neutral, PublicKeyToken=GAC_TOKEN" />

    </providers>

</roleManager>

 

  • Editar el web.config de la administración central de SharePoint 2010 las siguientes referencias con lo que se tendrá la posibilidad de agregar usuarios o roles desde el sitio de la administración central
<membership …>

  <providers>

    <add name="MyMembership" 

         type="ProjectName.Namespace.MyMembership, DllName, 

         Version=1.0.0.0,Culture=neutral, PublicKeyToken=GAC_TOKEN" />

  </providers>

</membership>

 
<roleManager … enabled="true">

    <providers>

        ...

        <add name="MyRoleManager" 

             type="ProjectName.Namespace.MyRoleProvider, DllName, Version=1.0.0.0,                    

             Culture=neutral, PublicKeyToken=GAC_TOKEN" />

    </providers>

</roleManager>

 

  • Editar el web.config del servicio de acceso de SharePoint 2010 las siguientes referencias con se podrá hacer el registro de los usuarios pertenecientes al custom membership provider, en las aplicaciones web. Este servicio se encuentra en el IIS en Sites->SharePoint WebServices->SecurityTokenServiceApplication
<system.web>

    <membership>

        <providers>

            <add name="MyMembership" 

                 type="ProjectName.Namespace.MyMembership, DllName, 

                 Version=1.0.0.0,Culture=neutral, PublicKeyToken=GAC_TOKEN" />

        </providers>

    </membership>

    <roleManager>

        <providers>

            <add name="MyRoleManager" 

                 type="ProjectName.Namespace.MyRoleProvider, DllName, Version=1.0.0.0,                    

                 Culture=neutral, PublicKeyToken=GAC_TOKEN" />

        </providers>

    </roleManager>

</system.web>

 

Con esto, ya debería funcionar nuestra autenticación personalizada en el sitio de SharePoint 2010.

 

Posibles incidencias

  • Error al hacer referencia al ensamblado del membership custom provider en el web.config
  • No poner las referencias en alguno de los web. Config
  • No tener referenciado el ensamblado en el GAC

 

Algunos enlaces interesantes: