Implementar un sitio web privado con ASP.NET MVC

24. December 2011 20:44 by Oscar.SS in Desarrollo Web  //  Tags: ,   //   Comments (5)

Bueno, para terminar el año operativo de este blog que mejor que un artículo de los facilitos. No es cuestión ya en estas fechas de complicarse la vida. Jejeje...que disculpas me pongo ;-)

Vamos a implementar con ASP.NET MVC la funcionalidad mínima para que un sitio web sea privado a todos los usuario no registrados. Utilizaremos la autentificación de usuarios por formulario y veremos como podemos aprovechar parte del código de la plantilla de aplicación que viene por defecto cuando creamos un nuevo proyecto ASP.NET MVC. Por otra parte crearemos un filtro personalizado que será el encargado de gestionar que usuarios tienen acceso para ejecutar un controlador o método de acción, o lo que es lo mismo, que usuarios tienen permiso para visualizar una vista concreta.

 

Paso 1. Creamos un nuevo proyecto ASP.NET MVC de tipo Internet Application

Seguramente el lector ya iniciado conocerá de sobra que esta plantilla de ASP.NET MVC contiene una funcionalidad mínima de un sitio web con un par de páginas y autenticación de usuarios por formulario que es precisamente la parte que nosotros vamos a reutilizar para el caso que nos ocupa. La validación de las credenciales de los usuarios que viene por defecto en esta plantilla utiliza el sistema de membresía de ASP.NET, el cual, nosotros vamos a sustituir más adelante.

 

Paso 2. Cambiar la ruta por defecto en el Global.asax

Parece lógico pensar que si todo el sitio web va a ser privado deberíamos iniciar la aplicación directamente en el formulario de login para que los usuarios escriban sus credenciales. Seguramente el lector ya sabrá que en el Global.asax existe un método RegisterRoutes() que es invocado al iniciar la aplicación para crear la tablas de rutas que utilizará el sistema de routing. En ese método podemos especificar que la ruta por defecto para nuestra aplicación será el formulario de login.

            routes.MapRoute(
                
"Default"// Route name
                
"{controller}/{action}/{id}"// URL with parameters
                
new { controller "Account", action "LogOn", id UrlParameter.Optional } // Parameter defaults
            
);

 

Paso 3. Validación de usuarios personalizada

Aquí es donde vamos a sustituir el sistema de membresía de ASP.NET por un método de validación de usuarios personalizado. Vayamos al controlador AccountController y modifiquemos de la siguiente forma el método de acción LogOn que maneja las peticiones por HTTP POST.

        public ActionResult LogOn(LogOnModel model)
        {
            
if (ModelState.IsValid)
            {
                
if (UserServices.ValidateUser(model.UserName, model.Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe)
;

                    return 
RedirectToAction("Index""Home");
                
}

                ModelState.AddModelError(
"""The user name or password provided is incorrect.");
            
}

            
// If we got this far, something failed, redisplay form
            
return View(model);
        
}

Si comparamos este código con el que viene por defecto veremos que tan solo se han modificado dos cosas. En primer lugar se ha eliminado el parámetro returnUrl (así como la comprobación de su formato) del método de acción. Cuando la autentificación de usuarios se gestiona directamente desde el Web.config, como hemos hecho siempre en ASP.NET, este parámetro contiene la ruta a la que intentaba navegar el usuario no autentificado. Dado que nosotros vamos efectuar este proceso por medio de un filtro global, no haremos uso de este parámetro.

En segundo lugar, hemos sustituido la validación de las credenciales del usuario por medio de la clase Membership por un sistema propio de validación. Vamos, esto no es más que un método que llame a nuestra base de datos, en este caso UserServices.ValidateUser(), y compruebe si existe un usuario con estas credenciales, y ya de paso que compruebe que este sea único...¡por si las moscas! :-)

 

Paso 4. Limpiamos el web.config

Por defecto el web.config viene configurado para hacer uso del sistema de membresía que hemos dicho que no vamos a utilizar, así que eliminamos del web.config las siguiente secciones.

  <!--Sustituimos la cadena de conexión que viene por defecto por la que corresponda-->
  
<connectionStrings>
    
<add name="ApplicationServices"
         connectionString
="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName
="System.Data.SqlClient" />
  </
connectionStrings>

  
<!--Administración y autenticación de cuentas de usuario-->
  
<membership>
    
<providers>
      
<clear/>
      
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"
           enablePasswordRetrieval
="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
           maxInvalidPasswordAttempts
="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
           applicationName
="/" />
    </
providers>
  
</membership>

  
<!--Configuración de los valores de los perfiles de usuario-->
  
<profile>
    
<providers>
      
<clear/>
      
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
    </
providers>
  
</profile>

 

Paso 5. Codificamos un filtro

El propio framework de ASP.NET MVC nos ofrece una serie de filtros  permitiéndonos la ejecución de código de forma limpia a modo de atributos declarativos. Como en otras ocasiones ASP.NET MVC nos permite extender el framework escribiendo un filtro personalizado que utilizaremos para permitir el acceso o no de los usuarios a los distintos recursos de la aplicación.

    public class RequiredAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
    {
        
/// <summary>
        /// Determina si va a realizar la comprobación de autenticación para un método de acción o 
        /// controlador. Por defecto es true.
        /// </summary>
        
public bool Check { get; set; }

        
public string Controller { get; set; }
        
public string Action { get; set; }

        
public RequiredAuthenticationAttribute()
        {
            Check 
= true;
        
}

        
public void OnAuthorization(AuthorizationContext filterContext)
        {
            
//Si es false no se realiza la comprobación de autenticación
            
if (!Check) return;

            
var routeValueDictionary = new RouteValueDictionary(
                
new
                    
{
                        
//El operador doble interrogación se invierte por validaciones de script en este blog
                        
action Action ¿¿ "LogOn",
                        controller 
Controller ¿¿ "Account"
                    
});

            if 
(!filterContext.HttpContext.Request.IsAuthenticated)
                filterContext.Result 
= new RedirectToRouteResult(routeValueDictionary);
        
}
    }

 

Paso 6. Registramos el filtro globalmente

Como nuestra intención es que todo el sito web sea privado para todos aquellos usuarios no registrados, en el Global.asax añadimos el filtro a la colección de filtros globales de ASP.NET MVC, de esta forma antes de ejecutar cualquier controlador se ejecutará el código de nuestro filtro personalizado. Y si el usuario que ejecuta el controlador no esta autenticado le devolverá siempre a la vista de login.

        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(
new HandleErrorAttribute());
            
filters.Add(new RequiredAuthenticationAttribute());
        
}

 

Paso 7. Definir métodos públicos a todos los usuarios

En el paso anterior hemos establecido que se ejecute el código de nuestro filtro antes de ejecutar cualquier controlador o método de acción. Pero tenemos que permitir el acceso al formulario de login a todos los usuarios para que puedan escribir sus credenciales. Para ello firmamos los dos métodos de acción (GET y POST) que gestionan el formulario de autenticación de usuarios.

        [RequiredAuthentication(Check = false)]
        
public ActionResult LogOn()
        {
            
return View();
        
}

        [HttpPost]
        [RequiredAuthentication(Check 
= false)]
        
public ActionResult LogOn(LogOnModel model)
        {
            
//Aquí va el código que hemos modificado en el paso 3
        
}

Como se puede observar, utilizamos el filtro y establecemos a false la propiedad Check para que todos los usuarios tengan permiso para visualizar y enviar el formulario de login. También podríamos especificar por medio de las propiedades Controller y Action del atributo un formulario de login distinto al de por defecto para redirigir a los usuarios que no tengan credenciales. 

 

Paso 8. Proteger carpetas y archivos

Con lo visto hasta ahora ya tendríamos la funcionalidad mínima para comprobar la autenticación de los usuarios. Pero supongamos que nuestra aplicación contiene documentos PDF, imágenes o cualquier tipo de archivo sensible de ser protegido. Dado que nuestro filtro sólo protege el contenido entregado por el propio método de acción, debemos especificar en el Web.config que carpetas o archivos deben ser protegidos. 

Por ejemplo, en el caso de una carpeta que contiene las fotos de los usuarios, si queremos que estas no sean accesibles directamente por URL a todos los usuarios debemos configurar la aplicación para protegerlas.

  <location path="UserPhotos">
    
<system.web>
      
<authorization>
        
<deny users="?"/>
      </
authorization>
    
</system.web>
  
</location>

 En combinación con los elementos location y deny, especificamos que el directorio que contiene las fotos de los usuarios sólo será accesible para los usuarios que ya se han registrado e identificado. Esta forma de proteger directorios no es exclusiva de MVC, de hecho, es la forma tradicional de hacerlo en ASP.NET.

Para finalizar mencionar que todo lo que hemos visto tan solo es un sencillo ejemplo para intentar mostrar algunos conceptos como los filtros, extensión del framework o la gestión de usuarios por formulario. De todas formas puede ser válido para pequeñas aplicaciones con pocos usuarios, pero para aplicaciones de más envergadura habría que realizar algo más de trabajo como por ejemplo con la gestión de roles.

Bueno, espero que a pesar de su sencillez le pueda servir a alguien para acercarse un poco más ASP.NET MVC y a todo lo que contiene.

¡¡Felices fiestas y hasta el año que viene!! :-)

 

Actualización 01/02/2013

Dado que varias personas me han pedido el código de la aplicación lo dejo aquí para descarga. En el encontraréis una solución ASP.NET MVC 3 con dos proyectos.

- ByCodingFilter. Este es el mismo proyecto que se ha visto a lo largo de este artículo.

- ByConfiguración. Establece la autorización por configuración en el web.config

PrivateMvcWebsite.rar (9.35 mb)

Comments (5) -

Gonzalo
Gonzalo
3/16/2012 4:17:15 PM #

hola, estaba leyendo tu post y me preguntaba si tienes el código del proyecto para bajarlo y revisar, ya que hay algunas cosas que no me resultan siguiéndolo online

Óscar.SS
Óscar.SS
3/16/2012 5:59:48 PM #

Hola Gonzalo,

Si, creo que tengo por ahí un proyectillo con este ejemplo pero ahora mismo estoy fuera de España y no tengo acceso a él. En cuento pueda te lo mando.

Muchas gracias por tu interés.

Un saludos

berenicestr
berenicestr
11/28/2012 4:22:03 PM #

Buenas, muchas gracias por el post, a mí tambié me interesaría bajarlo y poderlo revisar. Muchas gracias

cerios
cerios
5/16/2013 9:06:11 PM #

Hola Oscar,
No llevo mucho tiempo trabajando MVC y me interesa saber éste tema.
Mi pregunta es en el paso 5 (Codificamos un filtro).
El código que pones ahí en ese paso, en donde debe ir en la aplicacion.

Gracias

Oscar.SS
Oscar.SS
5/17/2013 8:35:08 AM #

Hola Cerios,

Bueno, para empezar decirte que si tienes una aplicación N-Layer por su puesto debe ir en tu capa UI o a veces llamada We, es decir, esto sería en este caso tu proyecto MVC dentro de la solución.

Ahora bien, el donde colocar este código dentro de tu proyecto Web, esto ya es más un tema de gustos personales. Yo normalmente creo una carpeta llamada Components y voy colocando ahí filtros, helpers, etc.

Pero ya te digo que es más una questión de gustos personales, porque conceptualmente un filtro no pertenece al Modelo o al Controlador. O dicho de otra forma, un filtro es algo que está entre estos dos mundos.

Si te descargas el código del ejemplo podrás comprobar lo que te comento.

Un saludo.

Recent Comments

Comment RSS

Month List