none
Validacion y autenticación de usuarios a nivel de controlador. RRS feed

  • Pregunta

  • Hola:

    He estado haciendo pruebas en mvc3 sobre seguridad. Aunque mvc3 implementa la funcionalidad necesaria para hacerlo, he visto que para la utilización de roles en las vistas, te obliga a declarar en la accion que usuarios tienen permisos de ejecución para la misma.

    He optado por otra opción, pero la verdad es que me gustaría contrastar si lo que he hecho es lo correcto, o simplemente tengo que seguir leyendo cosas sobra seguridad en mvc3.

    Lo que he hecho es crear una clase controller de la que heredan todos mis controladores que requieren autenticación.
    En ese control padre, sobreescribí el metodo OnActionExecuting, y contrasto si en el perfil del usuario autenticado tiene permitido la ejecución de dicha accion.

    Si tiene el permiso necesario cargo la vista, y si no es así, me gustaría lanzar una excepción que redireccione a la ruta definida en el web.config para esa excepcion.

    Todo funciona perfectamente (lo que no quiere decir que sea lo más correcto), pero cuando intento lanzar esa excepción desde OnActionExecuting y hacer la redirección a la url especificada en el web.config, no consigo hacerlo.

    Gracias de antemano.

    sábado, 30 de junio de 2012 23:35

Respuestas

  • Buenas,

    Eso que propones se puede hacer con un filtro de autorización, y además ya está implementado con un filtro que se llama AuthorizeAttribute. Su uso es muy simple:

    public FooController : Controller
    {
        [Authorize]
        public ActionResult OnlyAuthenticatedUsers() { ... }
    
        [Authorize(Roles="Admin")]
        public ActionResult OnlyAdmins() { .... }
    
        public ActionResult Public() { ... }
    
        [Authorize(Roles="Admin, Dev")]
        public ActionResult OnlyAdminsOrDevs() { ... }
    
        [Authorize(Users="Alice,Bob")]
        public ActionResult OnlyUsersAliceAndBob() { ... }
    }

    En el caso de que el usuario no cumpliese las condiciones especificadas en [Authorize] es automáticamente redirigido a la página de Login (a la URL indicada en el web.config).

    Saludos!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis


    lunes, 2 de julio de 2012 6:20
  • Hola BenitoCotito,

    Me alegro que te funcionara.

    Si me permites, un detallito de código.

    En lugar de usar:

    HttpContext.Current

    usa filterContext.HttpContext.

    La razón es muy simple: HttpContext.Current es la instancia al contexto ASP.NET actual (y por extensión al contexto ASP.NET MVC). Funciona perfectamente pero requiere de un pipeline ASP.NET en funcionamiento. Por otro lado filterContext.HttpContext es una instancia de HttpContextBase. Cuando tu código se ejecuta bajo IIS, la instancia de HttpContextBase se construye a partir de HttpContext.Current (de esto se encarga ASP.NET MVC), pero usar HttpContextBase te permite que tu código funcione sin pipeline de IIS asociado (p.ej. en pruebas unitarias), siempre y cuando crees un mock de HttpContextBase (por supuesto debes crear dicho mock, pero al menos puedes hacerlo, cosa que es imposible de hacer si usas HttpContext.Current).

    Un saludo!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis

    • Marcado como respuesta BenitoCotito martes, 3 de julio de 2012 15:40
    martes, 3 de julio de 2012 13:05

Todas las respuestas

  • Buenas,

    Eso que propones se puede hacer con un filtro de autorización, y además ya está implementado con un filtro que se llama AuthorizeAttribute. Su uso es muy simple:

    public FooController : Controller
    {
        [Authorize]
        public ActionResult OnlyAuthenticatedUsers() { ... }
    
        [Authorize(Roles="Admin")]
        public ActionResult OnlyAdmins() { .... }
    
        public ActionResult Public() { ... }
    
        [Authorize(Roles="Admin, Dev")]
        public ActionResult OnlyAdminsOrDevs() { ... }
    
        [Authorize(Users="Alice,Bob")]
        public ActionResult OnlyUsersAliceAndBob() { ... }
    }

    En el caso de que el usuario no cumpliese las condiciones especificadas en [Authorize] es automáticamente redirigido a la página de Login (a la URL indicada en el web.config).

    Saludos!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis


    lunes, 2 de julio de 2012 6:20
  • Hola Eduard:

    El problema es que no quiero definir en código los usuarios o roles que acceden a una acción. La idea es hacerlo desde una base de datos.

    Tal y como lo tengo ahora, lo que hago es controlar la carga de la acción en el basecontroller que me he creado. Tengo el usuario en la sesión y compruebo los permisos que tiene (/FooControler/Action). Si el usuario tiene el permiso (/FooControler/Action, sigo con la ejecución de la acción, en caso contrario lo redirecciono a la página de error.

    Un saludo

    lunes, 2 de julio de 2012 8:32
  • Buenas!

    Lo que haces está bien pero una pregunta:

    1. En run-time se "modifican" esos permisos? Es decir, un usuario X que antes NO podía hacer algo, pasa a poder hacerlo al cabo de un tiempo? (Debido a lo que sea)?

    Lo digo, porque si no, quizá es más óptimo usar roles de ASP.NET y [Authorize]. De este modo, cuando el usuario hace login en el sistema compruebas sus roles (que están en la BBDD). Evidentemente la asociación rol <-> acción es immutable (es decir, si los "admins" pueden hacer x siempre lo pueden hacer), al igual que la relación rol <->usuario (si un usuario es "admin" lo es siempre, aunque este punto se puede llegar a discutir).

    Si al final sigues con tu implementación, un par de apuntes:

    1. No necesitas sesión para nada. Guardar el usuario en sesión es una mala práctica en ASP.NET. Recuerda que tienes la cookie de autorización para eso y que el usuario está en User.Identity. Hace tiempo publiqué en mi blog un post sobre como modificar la cookie de autorización para guardar más datos en ella: http://geeks.ms/blogs/etomas/archive/2010/12/16/asp-net-obtener-el-id-del-usuario-actual.aspx
    2. En lugar de usar OnActionExecuting, considera usar e implementar un filtro de autorización (http://msdn.microsoft.com/es-es/library/gg416513(v=vs.98).aspx). Lo digo porque usando OnActionExecuting puedes tener problemas con la cache (puedes obtener respuestas cacheadas que se pueden dar a un usuario que NO está autorizado). Si en lugar de implementar un filtro quieres derivar de Controller lo puedes hacer ya que la clase Controller implementa todos los filtros posibles, pero en lugar de OnActionExecuting, mira el método OnAuthorization (http://msdn.microsoft.com/es-es/library/system.web.mvc.controller.onauthorization(v=vs.98).aspx)

    Un saludo!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis

    • Propuesto como respuesta Pedro Hurtado lunes, 2 de julio de 2012 13:46
    lunes, 2 de julio de 2012 9:52
  • Sisi, los permisos son editables desde la gestión de usuarios. Por eso no quiero prefijarlos en código. Esto se debe a la utilización de grupos y perfiles para los usuarios. Lo que hago es asignar un usuario a uno o n grupos, y a los grupos le asigno los permisos de los que disfrutan.

    Lo monté en el metodo onactionexecuting, porque de esta forma obtengo el nombre de la acción y el controlador que se ejecutan y puedo comprobar si cualquier grupo de ese usuario tiene el permiso necesario para poder utilizarlo.

    ¿Tu crees que debo ir por el filtro de autorización?

    Muchas gracias Eduard¡¡

    lunes, 2 de julio de 2012 10:23
  • Buenas!

    En OnAuthorize también tienes acceso a la acción y controlador. Los route values ya están cargados en este punto.

    Yo iría a través de un filtro de autorización, ya que lo que estás implementando es precisamente la autorización de una llamada.

    Un saludo!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis

    lunes, 2 de julio de 2012 13:09
  • Hola Eduard,

    Honestamente yo creo que este es el sitio.

    Si en lugar de implementar un filtro quieres derivar de Controller lo puedes hacer ya que la clase Controller implementa todos los filtros posibles, pero en lugar de OnActionExecuting, mira el método OnAuthorization (http://msdn.microsoft.com/es-es/library/system.web.mvc.controller.onauthorization(v=vs.98).aspx)


    phurtado
    Mi Blog Blog
    Sigueme en Twitter

    lunes, 2 de julio de 2012 13:45
  • Eduard, tenías toda la razón¡

    Lo he montado con un filtro y es muchisimo más limpio.

    Aun tengo que eliminar el session["Usuario"] y realizar el control de excepciones en lugar de hacer de hacer la redireción en el código, pero eso se que lo  haré rapido, así que os subo mi solución para el problema (aunque es más la solución de Eduard). :D


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using System.Subsistemas;
    public class CustomAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("AuthorizeFilterAttribute");
            }
    
            String username = filterContext.HttpContext.User.Identity.Name;  // 1
            List<string> userlist = Users.Split(',').ToList();                       // 2
    
    
            string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
            string action = filterContext.ActionDescriptor.ActionName;
    
            bool skipAuthorization =
                filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
                || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
                    typeof(AllowAnonymousAttribute), inherit: true);
    
            if (skipAuthorization)
            {
                return;
            }
    
            if (HttpContext.Current.Session["Cliente"] == null)
            {
                filterContext.Result = new RedirectResult("/Security/SinPermisos");
                return;
            }
    
            if (HttpContext.Current.Session["Usuario"] == null)
            {
                filterContext.Result = new RedirectResult("/Security/SinPermisos");
                return;
            }
    
            Usuario usuarioValidado = (Usuario)HttpContext.Current.Session["Usuario"];
    
            if (usuarioValidado.containsPermiso("/" + controller + "/" + action) == false)
            {
                //filterContext.Result = new RedirectResult("/Security/SinPermisos");
                //return;
                filterContext.Result = new HttpUnauthorizedResult();
                //throw new SecurityException();
            }
    
    
            //if (!userlist.Contains(username))
            //{                               // 3
            //    filterContext.Result = new HttpUnauthorizedResult();
            //}
        }
    }

    Aquí realizo la comprobación de los premisos asignados a los grupos de usuario.

    namespace System.Web.Mvc
    {
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                        AllowMultiple = false, Inherited = true)]
    
        public sealed class AllowAnonymousAttribute : Attribute
        {
    
        }
    }

    Este ultimo me permite definir una accion como [AllowAnonymous], de esta forma me evito tener que meter codigo inutil para comprobar que acciones son publicas o no.

    Muchisimas gracias Eduard, me ha sido de gran ayuda tu colaboración.

    Un saludo


    martes, 3 de julio de 2012 9:36
  • Hola BenitoCotito,

    Me alegro que te funcionara.

    Si me permites, un detallito de código.

    En lugar de usar:

    HttpContext.Current

    usa filterContext.HttpContext.

    La razón es muy simple: HttpContext.Current es la instancia al contexto ASP.NET actual (y por extensión al contexto ASP.NET MVC). Funciona perfectamente pero requiere de un pipeline ASP.NET en funcionamiento. Por otro lado filterContext.HttpContext es una instancia de HttpContextBase. Cuando tu código se ejecuta bajo IIS, la instancia de HttpContextBase se construye a partir de HttpContext.Current (de esto se encarga ASP.NET MVC), pero usar HttpContextBase te permite que tu código funcione sin pipeline de IIS asociado (p.ej. en pruebas unitarias), siempre y cuando crees un mock de HttpContextBase (por supuesto debes crear dicho mock, pero al menos puedes hacerlo, cosa que es imposible de hacer si usas HttpContext.Current).

    Un saludo!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis

    • Marcado como respuesta BenitoCotito martes, 3 de julio de 2012 15:40
    martes, 3 de julio de 2012 13:05
  • Tengo que darte las gracias otra vez. Tienes toda la razón¡¡

    Ahora mismo lo cambio y edito mi codigo.

    xD

    martes, 3 de julio de 2012 19:58