2010-02-04 15 views
15

Estaba leyendo otra pregunta sobre el bucle de inicio de sesión cuando tienes un usuario que inicia sesión, configurado para regresar a una URL a la que pueden no tener acceso después de iniciar sesión (es decir, una página de administración y el usuario inicia sesión con una cuenta).ASP.Net MVC cómo determinar si un usuario puede acceder a una URL?

La solución en WebForms parece ser utilizar el método UrlAuthorizationModule.CheckUrlAccessForPrincipal. Sin embargo, eso no funciona para las URL dirigidas a Métodos de acción asegurados con el Atributo Autorizado. Pensé que podría averiguar a qué método apuntaba la URL y reflexionar sobre ella para resolver mi problema, pero parece que no puedo averiguar cómo obtengo esta información de la tabla de enrutamiento.

¿Alguien ha trabajado alguna vez con esto o tiene una solución para esto? Si puedo obtener la información de la ruta desde una URL, creo que podría trabajar el resto, pero si alguien tiene una solución genérica, es decir. algún método oculto similar al antes mencionado para MVC, entonces eso sería totalmente increíble también.

No estoy preguntando cómo comprobar si el usuario tiene acceso a un par de controlador/acción especificado. Lo primero y más importante es encontrar la forma de obtener el par Controlador/Acción de la tabla de rutas basada en la URL. La razón de toda la historia de fondo es que existe un equivalente a UrlAuthorizationModule.CheckUrlAccessForPrincipal para MVC.

Respuesta

1

¿Cuál es el problema que está tratando de resolver? Parece que puede dirigirse hacia una solución compleja que podría usar una solución simple.

Si un usuario no tiene permisos para acceder a la página después de iniciar sesión, ¿desea que los usuarios que no iniciaron sesión vayan a una página, mientras que los usuarios que inician sesión van a una página diferente?

Si ese es el caso, podría estar tentado de crear otro controlador para esos casos y redirigir a ese controlador a cualquier lugar al que el usuario no tenga acceso. O si está utilizando su propio controlador base, pondría la funcionalidad allí.

A continuación, el controlador podría presentar la vista deseada. Por ejemplo, si un usuario que no ha iniciado sesión intenta acceder a una página, puede redirigirse a una página de error genérica. Si el usuario está conectado, podría ser redirigido a una página no autorizada.

Esto es muy similar a la respuesta de Robert.

Aquí hay un esqueleto básico para un controlador base.

public BaseController: Controller 
{ 

... // Some code 

    public ActionResult DisplayErrorPage() 
    { 
     // Assumes you have a User object with a IsLoggedIn property 
     if (User.IsLoggedIn())  
      return View("NotAuthorized"); 

     // Redirect user to login page 
     return RedirectToAction("Logon", "Account"); 
    } 

} 

Luego, en digamos un AdminController (que hereda de BaseController) acción

public ActionResult HighlyRestrictedAction() 
{ 
    // Assumes there is a User object with a HasAccess property 
    if (User.HasAccess("HighlyRestrictedAction") == false) 
     return DisplayErrorPage(); 

    // At this point the user is logged in and has permissions 
    ... 
} 
+0

Voy a aceptar esto por ahora, ya que parece ser la mejor solución por el momento. Sin embargo, no estoy seguro de que realmente use esto.Realmente me gusta la idea de no tener que tener este tipo de lógica integrada en mis controladores, también me gustaría evitar el redireccionamiento adicional. – kastermester

1

Esta es probablemente va a sonar controvertido, pero comprobación de seguridad al comienzo de cada método controlador, dentro del método:

public class ProductController : Controller 
{ 
    IProductRepository _repository 

    public ActionResult Details(int id) 
    { 
     if(!_repository.UserHasAccess(id)) 
      return View("NotAuthorized"); 

     var item = _repository.GetProduct(id); 

     if (item == null) 
      return View("NotFound"); 

     return View(item); 
    } 
} 

La razón por la que no utilizan los atributos [Authorize] de esto es que usted no puede pasar una identificación, o cualquier otra información de identificación, al atributo en tiempo de ejecución.

+0

Gracias, pero no estoy seguro de cómo este enfoque podría ayudarme - ver los comentarios que dejé para jfar. – kastermester

5

I de operador ya hackeado el código de la MvcSitemap:

public static class SecurityTrimmingExtensions 
{ 

    /// <summary> 
    /// Returns true if a specific controller action exists and 
    /// the user has the ability to access it. 
    /// </summary> 
    /// <param name="htmlHelper"></param> 
    /// <param name="actionName"></param> 
    /// <param name="controllerName"></param> 
    /// <returns></returns> 
    public static bool HasActionPermission(this HtmlHelper htmlHelper, string actionName, string controllerName) 
    { 
     //if the controller name is empty the ASP.NET convention is: 
     //"we are linking to a different controller 
     ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName) 
               ? htmlHelper.ViewContext.Controller 
               : GetControllerByName(htmlHelper, controllerName); 

     var controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerToLinkTo); 

     var controllerDescriptor = new ReflectedControllerDescriptor(controllerToLinkTo.GetType()); 

     var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName); 

     return ActionIsAuthorized(controllerContext, actionDescriptor); 
    } 


    private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 
    { 
     if (actionDescriptor == null) 
      return false; // action does not exist so say yes - should we authorise this?! 

     AuthorizationContext authContext = new AuthorizationContext(controllerContext); 

     // run each auth filter until on fails 
     // performance could be improved by some caching 
     foreach (IAuthorizationFilter authFilter in actionDescriptor.GetFilters().AuthorizationFilters) 
     { 
      authFilter.OnAuthorization(authContext); 

      if (authContext.Result != null) 
       return false; 
     } 

     return true; 
    } 

    private static ControllerBase GetControllerByName(HtmlHelper helper, string controllerName) 
    { 
     // Instantiate the controller and call Execute 
     IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 

     IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName); 

     if (controller == null) 
     { 
      throw new InvalidOperationException(

       String.Format(
        CultureInfo.CurrentUICulture, 
        "Controller factory {0} controller {1} returned null", 
        factory.GetType(), 
        controllerName)); 

     } 

     return (ControllerBase)controller; 
    } 

Se podría utilizar un poco de almacenamiento en caché, pero en mi caso que era una optimización prematura.

+0

Mi problema es que tengo una URL, no un controlador y una acción. El código que has escrito aquí es lo que creo que podría cocinar yo mismo, aunque gracias por compartirlo ya que probablemente necesite algo como esto. Pero la pregunta es: ¿cómo obtengo Controller + Action extraído de la RouteTable desde una URL? La razón por la que escribí mucho sobre las otras cosas fue porque sospechaba que podría haber un método para llamar para contarme todo esto de una vez. – kastermester

+0

@kastermester: para hacer eso, deberá interceptar la solicitud antes de que se ejecute el método del controlador. Para obtener ideas sobre cómo hacerlo, consulte aquí: http://stackoverflow.com/questions/2122459/how-malleable-are-the-conventions-in-asp-net-mvc/2122521#2122521 –

+0

Justo lo que necesitaba. He estado buscando alto y bajo para este. ¡Muchas gracias! –

0

Por qué no atribuir los métodos de su controlador con los requisitos de seguridad.

me escribió un atributo para hacer esto de la siguiente manera:

public class RequiresRoleAttribute : ActionFilterAttribute 
     { 
      public string Role { get; set; } 

      public override void OnActionExecuting(ActionExecutingContext filterContext) 
      { 
       if (string.IsNullOrEmpty(Role)) 
       { 
        throw new InvalidOperationException("No role specified."); 
       } 


       if (!filterContext.HttpContext.User.Identity.IsAuthenticated) 
       { 
        filterContext.HttpContext.Response.Redirect(loginUrl, true); 
       } 
       else 
       { 
        bool isAuthorised = filterContext.HttpContext.User.IsInRole(this.Role); 

        << Complete Logic Here >> 



       } 
      }  
     } 
+0

Mi problema es el siguiente: Un usuario va a mi página de inicio de sesión. Al ingresar, se establece una url de "retorno" y se transporta en la cadena de consulta. Supongamos por un momento que de alguna manera esa URL apunta a una parte de administrador del sitio. El usuario inicia sesión - con una cuenta normal. Ahora, usando este tipo de código (que yo soy), el usuario sería redirigido a la página de inicio de sesión, ¡pero ya ha iniciado sesión! Simplemente podría redireccionar a la página predeterminada, pero luego todos llegarían, también personas no autenticadas. Me gustaría verificar esto antes de redirigir después de iniciar sesión. – kastermester

1

En mi solicitud He creado un filtro personalizado derivado de AuthorizeAttribute, por lo que cualquier acceso no autorizado, simplemente ir a la página AccessDenied . Para enlaces, reemplazo Html.ActionLink con un helper personalizado Html.SecureLink. En esta extensión de ayuda, verifico si las funciones de este usuario tienen acceso a controlador/acción contra la base de datos. Si él/ella tiene la autorización, enlace de retorno en caso contrario devuelve el texto del enlace con observaciones especiales (podría ser la imagen/colorantes/js) respuesta

8

de jfar anterior actualizado para MVC 4:

public static class SecurityCheck 
{ 
    public static bool ActionIsAuthorized(string actionName, string controllerName) 
    { 
     IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 
     ControllerBase controller = factory.CreateController(HttpContext.Current.Request.RequestContext, controllerName) as ControllerBase; 
     var controllerContext = new ControllerContext(HttpContext.Current.Request.RequestContext, controller); 
     var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType()); 
     var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName); 
     AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor); 
     foreach (var authAttribute in actionDescriptor.GetFilterAttributes(true).Where(a => a is AuthorizeAttribute).Select(a => a as AuthorizeAttribute)) 
     { 
      authAttribute.OnAuthorization(authContext); 
      if (authContext.Result != null) 
       return false; 
     } 
     return true; 
    } 
} 
+0

O "foreach (var authAttribute en actionDescriptor.GetFilterAttributes (true) .OfType ())" - con disculpas por la pedantería. –

0

yo acabamos de pasar algunos tiempo implementando la solución de @ jfar (actualizándola para la versión no obsoleta de GetFilters()), y luego me di cuenta de que puedo omitir todo esto.

en mi caso (y supongo que en la mayoría de los casos) Tengo un AuthorizationAttribute personalizado para implementar la autorización del sitio que a su vez llama a mi servicio de autorizaciones para determinar el nivel de acceso real.

Así que en mi ayuda HTML para generar enlaces del menú, me he saltado derecho al servicio de autenticación:

@Html.MenuItem(@Url, "icon-whatever", "TargetController", "TargetAction") 

public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper, UrlHelper url,string iconCss, string targetController, string targetAction) 
{ 
    var auth = IoC.Resolve<IClientAuthorizationService>().Authorize(targetController, targetAction); 
    if (auth == AccessLevel.None) 
     return MvcHtmlString.Create(""); 

* usuario se determina dentro del servicio de autenticación de cliente

public string GetUser() { 
    return HttpContext.Current.User.Identity.Name; 
} 

* También se podría añadir un poco comportamiento para el acceso de solo lectura. es bueno porque mi servicio de autenticación se encarga de almacenar en caché, así que no tengo que preocuparme por el rendimiento.

Cuestiones relacionadas