miércoles, 30 de diciembre de 2009

ASP.NET Autenticación por Formulario (Parte II)

Puedes descargarte el ejemplo completo aquí.

En este post voy a explicar cómo manejar los roles de los usuarios con una autenticación basada en formularios.

En mi anterior post ASP.NET Autenticación por Formulario (Parte I) explico cómo crear una aplicación cuya seguridad está basada en la autenticación por formulario, con una sección de administración. El paso siguiente dentro de la seguridad de un sitio Web es aplicar significado a los roles de usuario, para ello vamos a crear diferentes regiones seguras dentro de la aplicación en las que el usuario tendrá acceso en función de sus roles asignados. En esta ocasión la autenticación se realiza contra una base de datos, a la cual accedemos mediante queries LINQ (ya explicaré cómo utilizar LINQ para el acceso a datos en un próximo post).

Voy a partir de la solución de mi anterior post que puedes descargar aquí.

He seguido lo siguientes pasos:

1.- He añadido una base de datos a mi solución una base de datos:

- Botón derecho sobre el proyecto - >Agregar > Nuevo elemento.

- Se selecciona "Base de Datos SQL Server".



2.- He creado las siguientes tablas:

- USUARIO: Contiene todos los usuarios registrados en nuestro sistema. Tiene los siguientes campos: ID_USUARIO, LOGIN, PASSWORD

- ROLE: Que contendrá todos los roles que necesita nuestra aplicación. Tiene los campos: ID_ROLE, DESCRIPCION.

- ROLES_USUARIO: Mantiene la relación n:m entre los usuarios y sus roles, permitiendo que un usuario pueda tener varios roles.

En el ejemplo que os presento existen dos roles (admin y user) y dos usuarios, así el usuario administrador tendrá todos los roles para poder acceder a todas las secciones de la aplicación.

 
 

3.- He agregado un Diagrama de Clases LINQ to SQL.

- Botón derecho sobre el proyecto -> Agregar > Nuevo elemento.

- Se selecciona "Clases de LINQ to SQL".


 

- Se rellena diagrama de clases LINQ (simplemente arrastrando la tabla desde el "Explorador de Servidores" hasta el diagrama de clases. Además he añadido las asociaciones entre las tablas obteniendo el resultado siguiente:


Se generarán así una serie de clases que nos permiten el acceso a la base de datos de una forma transparente, además de auto configurarse un ConnectionString.

4.- Creación de clases ADO para el acceso a los datos:

- He creado dos clases, Rol y Usuario:

La clase Rol está implementada así:

public class
Rol

{

AutenticacionDataClassesDataContext dc = new AutenticacionDataClassesDataContext();

const
char SI = 'S';

internal void AddRole (string description)

{

ROLE role = new
ROLE();

role.DESCRIPCION = description;

dc.ROLE.InsertOnSubmit(role);

dc.SubmitChanges();

}

internal
ROLE FindRol(Guid? codigoRole)

{

var roles = from u in dc.ROLE

where u.ID_ROLE == codigoRole

select u;

if (roles.Count() > 0)

return roles.First();

else

return null;

}

}

 
 

La clase Usuario tiene el siguiente código:

 
 

public
class
Usuario

{

AutenticacionDataClassesDataContext dc = new AutenticacionDataClassesDataContext();

const char SI = 'S';

internal void AddUsuario(string login, string password)

{

USUARIO usuario = new USUARIO();

usuario.LOGIN = login;

usuario.PASSWORD = password;

dc.USUARIO.InsertOnSubmit(usuario);

dc.SubmitChanges();

}

 internal USUARIO FindUsuario(Guid codigoUsuario)

{

var usu = from u in dc.USUARIO

where u.ID_USUARIO == codigoUsuario

select u;

if (usu.Count() > 0)

return usu.First();

else

return null;

}

internal
USUARIO FindUsuario(string login, string password)

{

var usu = from u in dc.USUARIO

where (u.LOGIN == login && u.PASSWORD == password)

select u;

if (usu.Count() > 0)

return usu.First();

else

return null;

}

internal void AddRole(Guid idUsuario, Guid idRole)

{

ROLES_USUARIO rolesUsuario = new ROLES_USUARIO();

rolesUsuario.ID_ROLE = idRole;

rolesUsuario.ID_USUARIO = idUsuario;

dc.ROLES_USUARIO.InsertOnSubmit(rolesUsuario);

dc.SubmitChanges();

}

internal List<ROLE> FindRolesUsuario(Guid? codigoUsuario)

{

List<ROLE> listRole = new
List<ROLE>();

var roles = from u in dc.ROLES_USUARIO

where u.ID_USUARIO == codigoUsuario

select u;

if (roles.Count() > 0)

{

IEnumerable<ROLES_USUARIO> rolesUsuario = roles.AsEnumerable<ROLES_USUARIO>();

foreach (ROLES_USUARIO roleUusario in rolesUsuario)

{

Rol rolADO = new
Rol();

ROLE rol = rolADO.FindRol(roleUusario.ID_ROLE);

if (rol != null)

{

listRole.Add(rol);

}

}

}

return listRole;

}

}

 
 

He implementado algunos métodos que no necesitaré en esta versión, no prestéis especial atención a como están implementados, no creo que sirvan de referencia como uso de LINQ :D.

 
 

5.- Implementación de la autenticación de usuarios:

 
 

Bien, una vez que tenemos listo nuestras clases de acceso a datos con los métodos que vamos a precisar para recuperar los usuarios y sus roles, podemos pasar a implementar la autenticación de los usuarios. He implementado el evento de autenticación de la siguiente forma:

 
 

protected
void Login1_Authenticate(object sender, AuthenticateEventArgs e)

{

string userName = Login1.UserName;

string password = Login1.Password;

bool rememberUserName = Login1.RememberMeSet;

Usuario usuarioADO = new Usuario();

USUARIO usuario = usuarioADO.FindUsuario(userName, password);

if (usuario != null)

{

List<ROLE> roles = usuarioADO.FindRolesUsuario(usuario.ID_USUARIO);

if (roles != null)

{

string rolesData = "";

foreach (ROLE role in roles)

{

rolesData += role.DESCRIPCION.ToString() + ";";

}

// Se crea el ticket de autenticación

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(

1, // Versión del ticket

userName,// Nombre de usuario asociado al ticket

DateTime.Now, // Fecha de creación del ticket

DateTime.Now.AddMinutes(50), // Fecha y Hora de las expiración de la cookie

rememberUserName, // Si el usuario cliquó en "Recuérdame" la cookie no expira.

rolesData, // Almacena datos del usuario, en este caso los roles

FormsAuthentication.FormsCookiePath); // El path de la cookie especificado en el Web.Config

// Se encripta la cookie para añadir más seguridad

string hashCookies = FormsAuthentication.Encrypt(ticket);

// Cookie encriptada

HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hashCookies);

// Se añade la cookie a la respuesta

Response.Cookies.Add(cookie);

// Se recupera la url que el usuario trataba de acceder

string returnUrl = Request.QueryString["ReturnUrl"];

// si no existe dicha url redirigimos al usuario a la pagina por defecto

if (returnUrl == null) returnUrl = "~/Default.aspx";

Response.Redirect(returnUrl);

}

}

else // usuario y password erróneos

{

// no se hace nada, el control Login mostrará automáticament los errores

}

}

 
 

Este código autentica a los usuarios contra nuestra base de datos y añade los roles del usuario.

 
 

6.- Creamos los directorios seguros en función de los roles:

Creamos los siguientes directorios:

 
 

- Admin: Destinado únicamente para usuarios administradores (usuarios con el rol 'admin').

- User: Destinado a los usuarios del sitio (con el rol 'user'). P.e: Este directorio podría contener las páginas donde el usuario gestiona su propia cuenta

Se securizan estos directorios modificando el Web.config de la siguiente forma:

 
 

<location path="Admin">

<system.web>

<authorization>

<allow roles="admin"/>

<deny users="*" />

</authorization>

</system.web>

</location>

<location path="User">

<system.web>

<authorization>

<allow roles="user"/>

<deny users="*"/>

</authorization>

</system.web>

</location>

 
 

7.- Creamos control de usuario:

Este control de usuario se añade para poder hacer pruebas de nuestra autenticación y se añade a la página master de nuestra aplicación (Ah! Sí también creé una página master para que este control de usuario esté disponible en todas las páginas de nuestro sitio).


8.- Modificamos el fichero Global.asax (si no existiese lo agregaríamos)

En este fichero se añade la gestión de la autenticación de un usuario, en el siguiente código:

 
 

protected void Application_AuthenticateRequest(object sender, EventArgs e)

{

// Si se envía alguna información acerca del usuario

if (HttpContext.Current.User != null)

{

// se comprueba que el usuario esté autenticado

if (HttpContext.Current.User.Identity.IsAuthenticated)

{

// Se comprueba si el usuario está autenticado por formulario

if (HttpContext.Current.User.Identity is FormsIdentity)

{

// Se recupera la identidad del usuario para recuperar sus roles

FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;

// Se recupera el ticket del usuario

FormsAuthenticationTicket ticket = identity.Ticket;

// Se recupera la información acerca del usuario, donde metimos la información de los roles

string[] roles = ticket.UserData.Split(';');

// Se crea un usuario con dichos roles

HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, roles);

}

}

}

}

 
 

¡Y esto es todo!

Puedes descargarte el ejemplo completo aquí.

19 comentarios:

  1. Muy Bueno tu trabajo muchas gracias

    ResponderEliminar
  2. Hola:

    Me podrias ayudar a ver como se haria la auntentificacion por roles en vb.net =)

    ResponderEliminar
  3. Muy buen tutorial, muchas gracias.

    ResponderEliminar
  4. Excelente trabajo, estoy entrando en la onda de .net y me fue de mucha ayuda. Gracias

    ResponderEliminar
  5. hola podriamos realizar una pregunta?
    como agregar con el tipo de dato uniqueidentifier

    ResponderEliminar
  6. No me abre la base de datos.

    ResponderEliminar
    Respuestas
    1. amigo tienes que quitarle la propiedad de solo lectura al archivo de base de datos ..... en App_Data se encuentra

      Eliminar
  7. Gracias parcero, habia buscado mucho para encontrar una solucion sencilla y muy funcional, consegui adaptarlo a lo que quería ..... saludos

    ResponderEliminar
  8. Excelente post
    http://www.dotnetfunda.com/articles/article141.aspx

    ResponderEliminar
  9. Consulta, cual es el Web.config que se modifica?? el que se creo en la parte I o el que viene por defecto en el proyecto???

    ResponderEliminar
  10. hola tengo un webform con pero tengo ub linkbutton para cuando se a olivado el password le den click en el botón y los mande a otro webform, pero no me deja por la authentication y me deja en la primera webform del login y no pasa al siguiente webform donde pido los datos para poder recuperar su password

    ResponderEliminar
  11. Se me apaga la pc porq el del cyber no me carga mas tiempo.... lpm...

    ResponderEliminar
  12. Hago copy y paste y no me funciona, devuélvanme mi dinero.

    ResponderEliminar