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í.