Este año pasado he dado mis primeros pasitos con ASP MVC, siendo también mi primera incursión en programación web. Lo primero que a lo que tuve que enfrentarme (tras pasar por varios tutoriales y leerme un par de libros) fue la gestión del usuario. La aplicación MVC compartía componentes con otras (WCF y Windows Forms) y la gestión del usuario y sus permisos se hacía ya con una implementación de IPrincipal. Una búsqueda por Internet me llevó a que debía usar para esto la vieja autenticación por formularios.
Para ello, primero debía crear mi nuevo proyecto MVC sin autenticación. Creé, a continuación, otro con autenticación para coger todo lo que pudiera del código y ahorrarme trabajo: casi todo el diseño e la vista de login, la vista parcial de _LoginPartial, su implementación en la cabecera… Todo lo que me pudiera suponer un ahorro de trabajo.
Lo siguiente fue crearme la clase que iba a utilizar en la cookie de autenticación:
Public Class UsuarioActivoSerializeModel Public Property Id As Integer Public Property IdSede As Integer Public Property Permisos As Long Public Property Name As String End Class
Tuve que decirle en el Web.config que iba a utilizar autenticación de formulario:
<system.web> (...) <authentication mode="Forms"> <forms loginUrl="~/Login/Login" timeout="2880" /> </authentication> </system.web>
Crear la cookie en al loguear al usuario:
<HttpPost> <AllowAnonymous> <ValidateAntiForgeryToken> Public Function Login(model As LoginViewModel, returnUrl As String) As ActionResult If Not ModelState.IsValid Then Return View(model) End If ' Validamos usuario AplicarConfiguracion() Dim mResult = GestorSGA.SetUsuario(model.Login, model.Password) If mResult.Id = 0 Then ' Login correcto System.Web.HttpContext.Current.User = Threading.Thread.CurrentPrincipal Dim mUsuarioActivo = TryCast(Threading.Thread.CurrentPrincipal.Identity, IUsuarioActivo) Dim mSerializeUsuario As New UsuarioActivoSerializeModel With {.Id = mUsuarioActivo.Id, .IdSede = mUsuarioActivo.IdSede, .Name = mUsuarioActivo.Name, .Permisos = mUsuarioActivo.Permisos} Dim mSerializer As New JavaScriptSerializer Dim mUserData As String mUserData = mSerializer.Serialize(mSerializeUsuario) Dim mAuthTicket As New FormsAuthenticationTicket(1, mUsuarioActivo.Login, DateTime.Now, DateTime.Now.AddHours(8), False, mUserData) Dim mEncryptedTicket As String = FormsAuthentication.Encrypt(mAuthTicket) Dim mCookie As New HttpCookie(FormsAuthentication.FormsCookieName, mEncryptedTicket) Response.Cookies.Add(mCookie) Return RedirectToLocal(returnUrl) Else ModelState.AddModelError("", "Intento de inicio de sesión no válido.") Return View(model) End If End Function
Y leerla en el evento PostAuthenticateRequest de la aplicación, donde descubrí que el HttpContext.Current.User y el Threading.Thread.CurrentPrincipal no eran la misma cosa y necesitaba que lo fueran, el primero para utilizarlo en la capa MVC y el segundo, en la lógica de negocio heredada. Nada grave. Agrego la gestión del evento en el Global.asax:
Private Sub MvcApplication_PostAuthenticateRequest(sender As Object, e As EventArgs) Handles Me.PostAuthenticateRequest Dim mCookie As HttpCookie = Request.Cookies(FormsAuthentication.FormsCookieName) If mCookie IsNot Nothing Then Dim mAuthTicket = FormsAuthentication.Decrypt(mCookie.Value) Dim mSerializer As New JavaScriptSerializer Dim mSerializeUsuario As UsuarioActivoSerializeModel = mSerializer.Deserialize(Of UsuarioActivoSerializeModel)(mAuthTicket.UserData) Dim mUsuarioActivo As New UsuarioActivo(mSerializeUsuario.Id, mSerializeUsuario.IdSede, mSerializeUsuario.Name, CType(mSerializeUsuario.Permisos, EPermisos), True) Dim mPrincipal As New SGAPrincipal(mUsuarioActivo) HttpContext.Current.User = mPrincipal Threading.Thread.CurrentPrincipal = HttpContext.Current.User End If End Sub
Lo siguiente es impedir el acceso a los usuarios no identificados, salvo en las acciones marcadas con <AllowAnonymous>. Para esto, agregamos a nuestro FilterConfig.vb:
Public Module FilterConfig Public Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection) filters.Add(New HandleErrorAttribute()) ' <AllowAnonymous> lo tenemos que añadir al login y ' a los puntos donde podamos acceder sin estar logueado. filters.Add(New AuthorizeAttribute()) End Sub End Module
Y, claro, el logoff:
<HttpPost> <ValidateAntiForgeryToken> Public Function LogOff() As ActionResult FormsAuthentication.SignOut() Session.Abandon() Return RedirectToAction("Index", "Home") End Function
Por último, quedaba implementar mi propia clase derivada de AuthorizeAttribute, pero de eso ya hablo otro día.