Autenticación de usuarios en ASP.NET MVC - Problemas con web hosting en clúster y balanceo de carga
En ciertas ocasiones, necesitamos que nuestra aplicación web ASP.NET disponga de un sistema de autenticación básica de usuarios para realizar, por ejemplo, tareas administrativas de configuración a través de un módulo de administración integrado que solo sea accesible para usuarios autenticados. Para este escenario en concreto, bastaría con implementar un simple sistema de Login para que el o los encargados de administrar la aplicación puedan autenticarse mediante su usuario y contraseña en el sistema.
En este Post veremos cómo crear un sistema de Login basado en FormsAuthentication
con encriptado de contraseña, y como integrarlo en una aplicación web ASP.NET MVC. También veremos como solucionar los problemas derivados de publicar nuestra aplicación en un Web hosting en clúster con balanceo de carga.
Nota: Antes de comenzar a desarrollar el Post, comentar que este ejemplo está desarrollado en ASP.NET MVC4 Framework 4.0 aunque también es perfectamente compatible con aplicaciones ASP.NET MVC5 Framework 4.5.
FormsAuthentication (Web.config)
En primer lugar crearemos el o los usuarios administradores de la aplicación. Este proceso lo realizamos en el elemento <authentication/>
, dentro de la sección <system.web/>
del archivo de configuración Web.config
de la aplicación.
<authentication mode="Forms">
<forms name=".ASPXAUTH" loginUrl="~/Login" timeout="10000">
<credentials passwordFormat="SHA1">
<user name="usuario" password="contraseña-encriptada" />
<!-- Otros usuarios aquí... -->
</credentials>
</forms>
</authentication>
mode="Forms"
: Habilita la autenticación mediante formularios.
name=".ASPXAUTH"
: Nombre de la Cookie que contendrá el Ticket de autenticación.
loginUrl="~/Login"
: URL de redirección, en el caso de que ASP.NET no encuentre la Cookie de autenticación.
timeout="10000"
: Tiempo de expiración de la Cookie de autenticación expresado en minutos.
passwordFormat="SHA1"
: Algoritmo de encriptación de las contraseñas de usuario (SHA1 hash).
name="admin"
: Nombre del usuario.
password="contraseña-encriptada"
: Contraseña del usuario encriptada en formato SHA1 (más adelante veremos como realizar el proceso de encriptación).
El Controlador y las Vistas
A continuación crearemos la infraestructura necesaria para desarrollar el sistema de autenticación y administración de la aplicación.
Creamos un Controlador básico con las Acciones necesarias para el sistema de Login y las tareas administrativas (posteriormente implementaremos el código para cada una de estas Acciones).
public class AdminController : Controller
{
// GET: Admin
public ActionResult Index()
{
// Aquí acceso al módulo de administración...
return View();
}
// GET: Admin/Login
public ActionResult Login()
{
// Aquí acceso a la 'Vista' de Login...
return View();
}
// POST: Admin/Login
[HttpPost]
public ActionResult Login(string usr, string pwd, string rme)
{
// Aquí lógica de autenticación...
return View();
}
// GET: Admin/Logout
public ActionResult Logout()
{
// Aquí lógica de desconexión...
return View();
}
}
Creamos también las Vistas (por supuesto siempre dentro del directorio de referencia del Controlador, Views\Admin\
). Para este ejemplo necesitaremos 2 Vistas: Login.cshtml
e Index.cshtml
.
Login.cshtml
: Vista que contendrá el formulario de autenticación de usuarios. Este sería un ejemplo básico de Login mediante usuario y contraseña con la opción Remember me, basado en Bootstrap 3.2:
<div class="container-fluid">
<div class="row">
<div class="col-sm-6 col-md-4 col-md-offset-4">
<div class="text-center">
<div class="text-center">
<img class="img-thumbnail" src="~/Images/admin.png" width="128">
</div>
<br />
@using (Html.BeginForm("Login","Admin"))
{
<input type="text" class="form-control" name="usr"
placeholder="Usuario" required autofocus>
<br />
<input type="password" class="form-control" name="pwd"
placeholder="Contraseña" required>
<br />
<button class="btn btn-lg btn-primary btn-block" type="submit">
Iniciar sesión
</button>
<label class="checkbox pull-left">
<input type="checkbox" name="rme" checked="">
<label>Remember me</label>
</label>
}
</div>
</div>
</div>
</div>
Index.cshtml
: Esta sería la Vista principal del módulo de administración. En un caso práctico podría ser un panel de control al estilo AdminLTE.
El código para la autenticación
Por último, solo quedaría codificar las Acciones del Controlador para dar la funcionalidad de autenticación al sistema de Login.
Esto lo haremos a través de la clase FormsAuthentication
del espacio de nombres System.Web.Security
. Esta clase proporciona una serie de métodos estáticos que interactuan con la configuración anteriormente establecida en el Web.config
<authentication/>
.
A continuación el código:
public class AdminController : Controller
{
// GET: Admin
public ActionResult Index()
{
if (this.Request.IsAuthenticated)
{
// Si el usuario está autenticado
// retorna la Vista de administración.
return View();
}
else
{
// Si no, retorna la Vista de Login.
return RedirectToAction("Login", "Admin");
}
}
// GET: Admin/Login
public ActionResult Login()
{
// Retorna la Vista de Login
return View();
}
// POST: Admin/Login
[HttpPost]
public ActionResult Login(string usr, string pwd, string rme)
{
if (string.IsNullOrEmpty(usr) || string.IsNullOrEmpty(pwd))
{
return View();
}
else
{
bool _rememberMe = rme == "on" ? true : false;
// Valida el usuario con los registrados en la
// seccion <authentication/> del Web.config
if (FormsAuthentication.Authenticate(usr, pwd))
{
// Crea la Cookie con el Ticket de autenticación
// para el usuario.
FormsAuthentication.SetAuthCookie(usr, _rememberMe);
// Redirige a la Accion 'Index'
return RedirectToAction("Index");
}
else
{
// Si no es un usuario válido, retorna la
// Vista de Login.
return View();
}
}
}
// GET: Admin/Logout
public ActionResult Logout()
{
// Elimina la Cookie con el Ticket de autenticación
// para el usuario.
FormsAuthentication.SignOut();
// Redirige a la Accion 'Index'
return this.RedirectToAction("Index");
}
}
Encriptado de contraseña
Siempre es una buena práctica encriptar las contraseñas y datos sensibles de nuestra aplicación, a la hora de publicarla en un Web hosting externo a nuestra infraestructura corporativa. En el caso que nos ocupa, habíamos configurado la sección <authentication/>
para utilizar contraseñas de usuario en formato SHA1 hash. Un ejemplo real con nombre de usuario 'admin' y contraseña 'admin', sería el siguiente:
<credentials passwordFormat="SHA1">
<user name="admin" password="D033E22AE348AEB5660FC2140AEC35850C4DA997" />
</credentials>
Existen infinidad de páginas en Internet que generan online la cadena encriptada de caracteres SHA1 a partir de un texto dado, http://www.sha1-online.com es un ejemplo de estas.
Publicando nuestra aplicación (Web hosting)
En este punto ya hemos integrado el sistema de autenticación a nuestra aplicación ASP.NET MVC y todo funciona correctamente en nuestro servidor IIS local o de desarrollo. Es la hora entonces de publicar la aplicación en los servidores web de nuestro proveedor de hosting.
Comenzamos a testear la aplicación, nos autenticamos en el sistema (Login), navegamos a través de las páginas que requieren usuarios autenticados, y en pocos minutos (o segundos) vemos como hemos perdido la autenticación y la aplicación nos redirige a la página de Login. Afortunadamente este es un problema muy común y de fácil solución.
El problema
Los proveedores de servicios de hosting funcionan con granjas de servidores web (Clustering) que implementan balanceo de carga para maximizar el rendimiento de las peticiones http y minimizar la carga de trabajo.
Cuando nos autenticamos en nuestra aplicación (Login), el servidor que atiende la petición crea un Ticket de autenticación que se almacena en una cookie que es enviada a nuestro navegador y nos permite navegar por las páginas que requieren usuarios autenticados.
Este Ticket de autenticación es creado a partir de que lo se llama un machineKey
(clave de máquina). Cuando nuestra aplicación arranca por primera vez en un servidor web, un machineKey
es creado automáticamente. Al autenticarnos, este machineKey
es utilizado para firmar digitalmente el Ticket de autenticación. Por otra parte, cuando navegamos a una página que requiere usuarios autenticados, el servidor utiliza el machineKey
para desencriptar el Ticket de autenticación de la Cookie para validar el usuario.
El problema surge cuando es un servidor diferente (por el balanceo de carga) el que atiende la petición de autenticación, e intenta desencriptar el Ticket de autenticación con un machineKey
diferente. El resultado de esta acción causaría la invalidación del Ticket de autenticación original (y de la Cookie), y la redirección del usuario a la página de Login.
La solución
ASP.NET nos permite definir un machineKey
personalizado para cada una de nuestras aplicaciones web. Esto implica que los servidores donde se ejecute la aplicación, no auto-generen uno propio y utilicen el que la aplicación les propone.
La configuración del machineKey
se realiza dentro de la sección <system.web/>
del archivo de configuración Web.config
de la aplicación:
<machineKey validationKey="B2A3A7ED71A85AC9BCBBBAD8407F9642EEFA3B17FD2583CFD66A3BAF407900D2C45A85379FA2B4BE2397BECF14650306B6292C96418B772055F01E1EE751E434"
decryptionKey="E60EC68AB3752322D236E5727370620398818355316FB5FDFBF00EE4344AE7B7"
validation="SHA1" decryption="AES" />
Es recomendable generar un machineKey
aleatorio (y diferente) para cada una de la aplicaciones que queramos publicar en nuestro Web hosting. Desde este sitio web: ASP.Net MachineKey Generator podemos generar nuestros machineKey
en función del Framework ASP.NET que estemos utilizando.
Nuevo comentario
Comentarios
No hay comentarios para este Post.