martes, 12 de marzo de 2013

ASP.NET: Cómo personalizar la seguridad "Forms" para usar nuestra tablas de usuarios-roles.

Hoy veremos como personalizar algunos aspectos de la gestión de accesos, roles y usuarios en nuestro site

ASP.NET

. Antes de nada comentaremos sin entrar en detalle las posibilidades que ofrece

ASP.NET

para autentificar y autorizar a los clientes de un site:
  • Forms: Recomendado si los usuarios del site no se encuentran bajo un mismo dominio y el site contiene partes públicas.
  • Passport
  • Windows: Recomendado cuando los usuarios del site se encuentran bajo el mismo dominio.
Podemos especificar que tipo de seguridad queremos aplicar vía archivo web.config o bien usando la herramienta de administración que proporciona ASP.NET desde el menú Proyecto.

areaTIC, ASP.NET, Menú Proyecto Visual Studio

areaTIC, ASP.NET, Herramienta Configuración ASP.NET

Si vamos a la pestaña seguridad allí se explica perfectamente el medio de almacenamiento por defecto que se usa para la información de usuarios y roles. En resumen, por defecto la información si no hemos cambiado el provider se almacena en una instancia de SQLEXPRESS del servidor.

Llegado a este punto, ¿qué pasa si la empresa donde estamos configurando el site dispone de más aplicaciones donde hay una base de datos ya con tablas para la gestión personalizada de usuarios/roles y queremos aprovechar el mismo formato y contenido de las tablas?

A continuación veremos como configurar nuestro areaTICAuthenticationModule personalizado que se encargará de realizar la gestión de accesos y permisos en cada petición que llegue al servidor.

En primer lugar desde la página de login tendremos que generar un

FormsAuthenticationTicket

que contendrá la información del usuario y sus roles. En el ejemplo, trabajaremos con un modelo de información como el que se muestra en la imagen. Al hacer login suponemos que el servidor conecta a la base de datos donde está la información de usuarios y devuelve un objeto LoginReponse con la información del usuario y sus roles.

areaTIC, ASP.NET, Menú Proyecto Visual Studio

Veamos como generar el

FormsAuthenticationTicket

desde nuestra página de login.
FormsAuthenticationTicket authTicket = null;

  // Ojo porque si los accesos superan los 1000 caracteres se pierde la información.
  string[] userData = (from RolUsuario r in LoginResponse.Roles select r.Rol).Distinct().ToArray<string>();
  
  string authTicketUser = "";

  if (LoginResponse.Usuario != null)
    authTicketUser = LoginResponse.Usuario.Usuario;

  authTicket = new FormsAuthenticationTicket(0, authTicketUser, DateTime.Now, DateTime.Now.AddMinutes(20), false, String.Join(";", userData));
  
  string crypTicket = FormsAuthentication.Encrypt(authTicket);

  HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, crypTicket);
  HttpContext.Current.Response.Cookies.Add(authCookie);
  HttpContext.Current.Response.Redirect(FormsAuthentication.GetRedirectUrl(authTicketUser, false), false);
Antes de implementar el module estaría bien guardarnos en un objeto de aplicación la relación RolPagina para evitar tener que consultarla en base de datos cada vez que se realice una petición.
  public class Global : System.Web.HttpApplication
  {
    void Application_Start(object sender, EventArgs e)
    {
      List<RolPagina> RolPagina = ...;
      Application["RolPagina"] = RolPagina;
    }
  }
Veamos ahora como implementar el module.
namespace areaTIC.Code.Modules
{
 public sealed class areaTICAuthenticationModule : IHttpModule
 {
   HttpApplication app = null;

   public void Dispose()
   {

   }

   public void Init(HttpApplication context)
   {
     this.app = context;
     app.AuthorizeRequest += new EventHandler(this.OnAuthorize);
     app.AuthenticateRequest += new EventHandler(this.OnAuthenticate);
   }

   private void OnAuthorize(object sender, EventArgs e)
   {
     Configuration config = WebConfigurationManager.OpenWebConfiguration("/appSettings");
     //Obtenemos la lista de RolesPagina que del objeto de aplicación que hemos cargado en el paso anterior.
     List<RolPagina> RolesPorPagina = (List<RolPagina>)HttpContext.Current.Application["RolPagina"];

     if (RolesPorPagina != null)
     {
        List<Rol> Roles = (from RolPagina Roles in RolesPorPagina 
           where Roles.Pagina.Equals(HttpContext.Current.Request.Path)
           select Rol).ToList<Rol>();

        if (Roles.Count > 0)
        {
           //Si no está autentificado lo enviamos al login
           if (HttpContext.Current.User == null || !HttpContext.Current.User.Identity.IsAuthenticated)
              HttpContext.Current.Response.Redirect(string.Format("~/PaginaLogin.aspx?ReturnURL=~{0}", HttpContext.Current.Request.Path));

           IPrincipal principal = HttpContext.Current.User as IPrincipal;
           
           foreach(Rol r in Roles)
           {
             //Comprobamos si tiene asignado algún rol con acceso a la página solicitada.
             if (principal.IsInRole(r)
               HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Path);
           }
           
           //si llegamos a este punto quiere decir que no dispone del rol para acceder a la página solicitada.
           HttpContext.Current.Response.Redirect("PaginaSinAcceso.aspx");
        }
    }  

    private void OnAuthenticate(object sender, EventArgs e)
    {
     Configuration config = WebConfigurationManager.OpenWebConfiguration("/appSettings");

     //Si no está autentificado lo enviamos al login
     if (HttpContext.Current.User == null || !HttpContext.Current.User.Identity.IsAuthenticated)
       HttpContext.Current.Response.Redirect(string.Format("~/Account/LoginNet.aspx?ReturnURL=~{0}", HttpContext.Current.Request.Path));

     //Recuperamos ticket 
     FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
     FormsAuthenticationTicket ticket = identity.Ticket;

     IPrincipal principal = new GenericPrincipal(identity, ticket.UserData.Split(';'));

     //Se crea la clase Principal y se asigna al CurrentUser del Contexto
     HttpContext.Current.User = principal;
     Thread.CurrentPrincipal = principal;
         
    }
  }
}
Por último tenemos que registrar el module desde el archivo web.config tal y como vimos en este artículo.

Con esto ya tendríamos las bases para personalizar la seguridad Forms de nuetro site

ASP.NET

, seguramente si os decantáis por este sistema tendréis que ampliar este ejemplo pero espero os pueda servir como punto de partida en algún caso. Recordaros como siempre que podéis seguir

areaTIC

en las redes sociales y enviarnos vuestras valoraciones y/o comentarios. Anímate a participar!


1 comentario:

Anónimo dijo...

Algo asi buscaba, pero podrias poner algo mucho mas simple, donde simplemente se tengamos usuario y roles y declaremos los permisos en el web.config.

Un saludo.

Publicar un comentario en la entrada