Estoy creando una aplicación web MVC que, al menos, parte de su transferencia de datos depende de Ajax.

La acción del controlador es

[RBAC]
[Authorize]
public string GetData(string inputdata)
{
   some code ...
   return jsondata;
}

La llamada ajax es

 $.ajax({
       dataType: "json",
       url: Url,
       data: { '_inputdata': selectedText },
       success: function (data)
       {
           response($.map(data,
              function(item, index) {
              return {
                   label: item.label,
                   value: item.value
               }
            }));
       },
      error: (function (jqXHR, textStatus, errorThrown, data) {
           ProcessFail(jqXHR, textStatus, errorThrown,  data);
        });
      })
  }); 

[RBAC] hace que se realice una verificación de autorización, que es lo que quiero.

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
      ......
         filterContext.Result = new RedirectToRouteResult
              (new RouteValueDictionary { { "action", "Index" }, 
              { "controller", "Unauthorised" } , 
              { "Area", String.Empty }});
       .....
    } 

El problema es que no obtengo nada en el ajax excepto un fallo. No hay nada que me diga que hubo un error de autorización.

Preguntas:

  1. ¿Es posible recuperar información de una falla de autorización en la respuesta ajax? ¿Si es así, cómo?
  2. Si la respuesta a 1. es no, ¿debo verificar esta autorización antes de hacer esta llamada?

Como siempre, cualquier ayuda apreciada.

2
BrownPony 27 dic. 2016 a las 22:04

3 respuestas

La mejor respuesta

Esta es una solución completa que le permite decorar esencialmente sus acciones con una sola llamada dentro de su acción que funciona de la misma manera que la autenticación basada en formularios estándar en ASP.net.

Simplemente copie la PC aquí y debería funcionar.

Ese problema es que el código de autorización que se implementa decorando la acción no devuelve un error de autorización al Ajax.

Así que

[Authorize] or in my case [RBAC]
public string SomeActionCalledByAjax( some args)
{
   some stuf
}

Falla sin mensaje de error para el usuario.

Aquí está la solución que implementé. Realmente utiliza la OnAuthorization.
Mi objetivo era obtener una solución simple que me permitiera decorar las acciones casi como el código de autorización de fábrica. He tenido éxito en esto.

Crédito a

¿Cómo obtengo el MethodInfo de una acción, acción dada, controlador y nombres de área? crédito a Miguel Angelo.

Y

Manejo de errores de jQuery Ajax, muestra mensajes de excepción personalizados

Crédito AlexMAS

Nunca lo habría descubierto si no fuera por estos muchachos.

Estoy usando RBAC por seguridad. Encuéntralo aquí. https: // www. codeproject.com/articles/1079552/custom-roles-based-access-control-rbac-in-asp-ne

Excelente seguridad basada en roles. buen sistema Extiende la autenticación basada en formularios a través del marco de identidad de ASP.NET.

Entonces, esto habría sido simple si pudiera ver IPrincipal.User fuera del controlador, pero descubrí que no podía pasarlo a un método en el controlador y aún así ver las extensiones que se usaron para RBAC que obtienen los permisos en ese método.

Pero puedes verlo aquí.

public class RBACAttribute:AuthorizeAttribute
{
   public override void OnAuthorization(AuthorizationContext filterContext)
   {
      do stuff.
   }
}

Entonces, el truco se centra en cómo obtener un filtro de texto de AuthorizationContext lleno correctamente y luego puedo llamar a OnAuthorize.

Aquí es donde entra el código de Miguel. Es una extensión del controlador. Lo cambié un poco porque en realidad obtendrá toda su información de la referencia del controlador que se pasa. Solo quiero el ActionDescriptor para poder llenar un objeto AuthorizationContext

public static class GetControllerAttr
    {
        public static ActionDescriptor GetActionAttributes(this Controller @this,string action,string controller,string area,string method)

        {
           var actionName = action ?? @this.RouteData.GetRequiredString("action");
            var controllerName = controller ?? @this.RouteData.GetRequiredString("controller");
            var areaName = area ?? @this.RouteData.Values [ "area" ] ;
            var methodName = method  ?? @this.RouteData.GetRequiredString("action");
            var controllerFactory = ControllerBuilder.Current.GetControllerFactory();

            var controllerContext = @this.ControllerContext;

            var otherController = (ControllerBase)controllerFactory
                .CreateController(
                    new RequestContext(controllerContext.HttpContext,new RouteData()),
                    controllerName);

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

            var controllerContext2 = new ControllerContext(
                new MockHttpContextWrapper(
                    controllerContext.HttpContext.ApplicationInstance.Context,
                    methodName),
                new RouteData(),
                otherController);

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

            return actionDescriptor ;
            //var attributes = actionDescriptor.GetCustomAttributes(true)
            //    .Cast<Attribute>()
            //    .ToArray();

            //return attributes;
        }
    }
    class MockHttpContextWrapper:HttpContextWrapper
    {
        public MockHttpContextWrapper(HttpContext httpContext,string method)
            : base(httpContext)
        {
            this.request = new MockHttpRequestWrapper(httpContext.Request,method);
        }

        private readonly HttpRequestBase request;
        public override HttpRequestBase Request
        {
            get { return request; }
        }

        class MockHttpRequestWrapper:HttpRequestWrapper
        {
            public MockHttpRequestWrapper(HttpRequest httpRequest,string httpMethod)
                : base(httpRequest)
            {
                this.httpMethod = httpMethod;
            }

            private readonly string httpMethod;
            public override string HttpMethod
            {
                get { return httpMethod; }
            }
        }
    }

Tomé el código de Alex, lo modifiqué un poco para obtener la información que quería enviar a JQuery

  public class ClientErrorHandler:FilterAttribute, IExceptionFilter
    {
        public void OnException(ExceptionContext filterContext)
        {
            var response = filterContext.RequestContext.HttpContext.Response;

            clsAuthorizationError _clsAuthorization = new clsAuthorizationError();
            if(filterContext.Exception.Data.Contains("ErrorCode"))
            {
                _clsAuthorization.ErrorCode = (int)filterContext.Exception.Data["ErrorCode"];
                _clsAuthorization.ReDirect = filterContext.Exception.Message;
                string _results = JsonConvert.SerializeObject(_clsAuthorization);
                response.Write(_results);

            }
            else
            {
                response.Write(filterContext.Exception.Message);
            }

            response.ContentType = MediaTypeNames.Text.Plain;


            filterContext.ExceptionHandled = true;

        }
    }
    public class clsAuthorizationError
    {
        public int ErrorCode { set; get; }
        public string ReDirect { set; get; }
    }

En el método de OnAuthorization anulado, agregué la cadena Url y el código de error.

   public class RBACAttribute:AuthorizeAttribute
    {
      public string Url { set; get; }
      public int ErrorCode { set; get; }
      public override void OnAuthorization(AuthorizationContext filterContext)
      {
          Url = null;

          string _action = null;
          string _controller = null;
          try
          {
           if(!filterContext.HttpContext.Request.IsAuthenticated)
           {
              //Redirect user to login page if not yet authenticated.  This is a protected resource!

             filterContext.Result = new RedirectToRouteResult(new 
                   RouteValueDictionary { { "action",_action },
                                  { "controller",_controller },
                                  { "Area",String.Empty } });
               Url =   "/__controller__/__action__/";
               Url = Url.Replace("__controller__",_controller);
               Url = Url.Replace("__action__",_action);
                ErrorCode = 401;
            }
            else
            {
             //Create permission string based on the requested controller name and action name in the format 'controllername-action'
                string requiredPermission = String.Format("0}-{1}",
                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                   filterContext.ActionDescriptor.ActionName);
              if(!filterContext.HttpContext.User.HasPermission(requiredPermission) & !filterContext.HttpContext.User.IsSysAdmin())
{
    _action = "Index";
    _controller = "Unauthorised";
     //User doesn't have the required permission and is not a SysAdmin, return our custom “401 Unauthorized” access error
     //Since we are setting filterContext.Result to contain an ActionResult page, the controller's action will not be run.
    //The custom “401 Unauthorized” access error will be returned to the browser in response to the initial request.
     filterContext.Result = new RedirectToRouteResult(
                   new RouteValueDictionary { { "action",_action },
                          { "controller",_controller },
                              { "Area",String.Empty } });
             Url =   "/__controller__/__action__/";
             Url = Url.Replace("__controller__",_controller);
             Url = Url.Replace("__action__",_action);
             ErrorCode = 401;
             }
   //If the user has the permission to run the controller's action, the filterContext.Result will be uninitialized and
   //executing the controller's action is dependant on whether filterContext.Result is uninitialized.
           }
         }
          catch(Exception ex)
          {
              _action ="Error";
              _controller = "Unauthorised";
              Url =   "/__controller__/__action__/";
              Url = Url.Replace("__controller__",_controller);
              Url = Url.Replace("__action__",_action);
              filterContext.Result = new RedirectToRouteResult(
                       new RouteValueDictionary(
                       new { controller = _controller,action = _action,
                          _errorMsg = ex.Message })
              ErrorCode = 500;

                }
              }
            }

En la llamada Ajax agregue lo siguiente.

complete: function (jqXHR, textStatus, errorThrown)
{               
   var jparse = JSON.parse(jqXHR.responseText);
   if (jparse.hasOwnProperty('ErrorCode'))
   {            
        var code = jparse.ErrorCode;
        var href = jparse.ReDirect;
         window.location.href = href;
   }
}

Luego creé un front-end para armar las PC

Clase pública clsOnAuthorization {

            //private string Redirect { set; get; }

            //string _Action { set; get; }
            //string _Controller { set; get; }
            //string _url { set; get; }
            AuthorizationContext _filterContext;

            public clsOnAuthorization(Controller @this)
            {         
                _filterContext = new AuthorizationContext(@this.ControllerContext,GetControllerAttr.GetActionAttributes(@this,null,null,null,null));

                Verify ( ) ;

            }
            public void Verify()
            {
                RBACAttribute _rbacAttribute = new RBACAttribute();
                _rbacAttribute.OnAuthorization(_filterContext);
                if(_rbacAttribute.Url != null)
                {
                    Exception myEx = new Exception(_rbacAttribute.Url);
                    myEx.Data.Add("ErrorCode", _rbacAttribute.ErrorCode); 

                    throw myEx;
                }
            }     
        }

Finalmente, decoro la acción y hago una llamada en la acción.

[ClientErrorHandler]
public string JobGuid()
{
     // send controller in with constructor.
   clsOnAuthorization _clsOnAuthorization = new clsOnAuthorization(this);
    //  if authorization fails it raises and exception and never comes back here. 
    some stuff if authorization good.
} 

Con una decoración y una instanciación de clase única, todos los problemas de autorización desaparecieron y mis llamadas ajax ahora saben qué salió mal y pueden redirigirse adecuadamente.

2
Community 23 may. 2017 a las 11:46

Parece que está utilizando MVC en lugar de API web, la API web debería darle un buen mensaje JSON de forma predeterminada.

Una opción sería verificar el código de estado de la respuesta, esto debería darle un 401 si se trata de un error de autenticación.

Otro sería eliminar el [Autorizar] y hacer una verificación dentro del método mismo

public string GetData(string inputdata)
{
   if (User.Identity.IsAuthenticated) { 
      return  jsonData;
   } 
   return failureJson;
}

Nota: estoy seguro de que hay una forma más elegante de hacer esto, pero esto debería funcionar

1
Zach 27 dic. 2016 a las 19:51

Use otro parámetro complete: como success: y error: para verificar el fallo de autorización en su llamada $.ajax(). Después de success implementa este fragmento de código. aquí el código 401 muestra un error de autorización. Compruebe si la condición.

success: function (data)
       {
           response($.map(data,
              function(item, index) {
              return {
                   label: item.label,
                   value: item.value
               }
            }));
       },
       complete: function(jqXHR){
                 if (jqXHR.status== '401'){
                     **//Your code here whatever you want to do**
                    }
}

complete: se devuelve cada vez que se completa su llamada ajax.

0
Rajput 30 dic. 2016 a las 13:57