2012-09-27 24 views
29

Estoy heredando de System.Web.Http.AuthorizeAttribute para crear una rutina de autorización/autenticación personalizada para cumplir con algunos requisitos inusuales para una aplicación web desarrollada con ASP.NET MVC 4. Esto agrega seguridad a la API web utilizada para llamadas Ajax desde el cliente web. Los requisitos son:Cómo personalizar API web ASP.NET AuthorizeAttribute para requisitos inusuales

  1. El usuario debe iniciar sesión cada vez que realizan una transacción para verificar otra persona no ha caminado hasta la estación de trabajo después de que alguien tiene iniciar sesión y se alejó.
  2. Los roles no se pueden asignar a los métodos del servicio web en el momento del programa. Deben asignarse en tiempo de ejecución para que un administrador pueda configurar esto. Esta información se almacena en la base de datos del sistema.

El cliente web es un single page application (SPA) por lo que la autenticación de formularios típica no funciona tan bien, pero estoy intentando reutilización como gran parte del marco de seguridad de ASP.NET como pueda para cumplir con los requisitos. El AuthorizeAttribute personalizado funciona muy bien para el requisito 2 sobre la determinación de los roles asociados con un método de servicio web. Acepto tres parámetros, nombre de la aplicación, nombre del recurso y operación para determinar qué roles están asociados con un método.

public class DoThisController : ApiController 
{ 
    [Authorize(Application = "MyApp", Resource = "DoThis", Operation = "read")] 
    public string GetData() 
    { 
     return "We did this."; 
    } 
} 

I reemplazar el método OnAuthorization para obtener las funciones y autenticar al usuario. Dado que el usuario debe ser autenticado para cada transacción, reduzco el parloteo hacia adelante y hacia atrás mediante la autenticación y la autorización en el mismo paso. Obtengo las credenciales de los usuarios del cliente web mediante el uso de autenticación básica que pasa las credenciales encriptadas en el encabezado HTTP. Así que mi método OnAuthorization se ve así:

public override void OnAuthorization(HttpActionContext actionContext) 
{ 

    string username; 
    string password; 
    if (GetUserNameAndPassword(actionContext, out username, out password)) 
    { 
     if (Membership.ValidateUser(username, password)) 
     { 
      FormsAuthentication.SetAuthCookie(username, false); 
      base.Roles = GetResourceOperationRoles(); 
     } 
     else 
     { 
      FormsAuthentication.SignOut(); 
      base.Roles = ""; 
     } 
    } 
    else 
    { 
     FormsAuthentication.SignOut(); 
     base.Roles = ""; 
    } 
    base.OnAuthorization(actionContext); 
} 

GetUserNameAndPassword recupera las credenciales de la cabecera HTTP. Luego uso Membership.ValidateUser para validar las credenciales. Tengo un proveedor de membresía personalizado y un proveedor de roles conectado para acceder a una base de datos personalizada. Si el usuario está autenticado, entonces recupero los roles para el recurso y la operación. Desde allí utilizo la base OnAuthorization para completar el proceso de autorización. Aquí es donde se descompone.

Si el usuario está autenticado, utilizo los métodos de autenticación de formularios estándar para iniciar sesión en el usuario (FormsAuthentication.SetAuthCookie) y, si no lo logro, lo cierro (FormsAuthentication.SignOut). Pero el problema parece ser que la base OnAuthorization clase no tiene acceso a Principal que se actualiza para que IsAuthenticated se establezca en el valor correcto. Siempre está un paso atrás. Y mi suposición es que está usando algún valor almacenado en caché que no se actualiza hasta que haya un viaje de ida y vuelta al cliente web.

Así que todo esto lleva a mi pregunta específica que es, ¿hay otra manera de establecer IsAuthenticated al valor correcto para la corriente principal sin el uso de las cookies? Me parece que las cookies realmente no se aplican en este escenario específico en el que tengo que autenticar cada vez.La razón por la que sé IsAuthenticated no está establecido en el valor correcto es también anular el método HandleUnauthorizedRequest a esto:

protected override void HandleUnauthorizedRequest(HttpActionContext filterContext) 
{ 
    if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated) 
    { 
     filterContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden); 
    } 
    else 
    { 
     base.HandleUnauthorizedRequest(filterContext); 
    } 
} 

Esto me permite devolver un código de estado Prohibido el cliente web si el fallo fue por autorización en lugar de autenticación y puede responder en consecuencia.

Entonces, ¿cuál es la forma correcta de establecer IsAuthenticated para la corriente Principio en este escenario?

Respuesta

33

La mejor solución para mi situación parece ser pasar por alto la base OnAuthorization completamente. Como tengo que autenticar cada vez, las cookies y el almacenamiento en caché del principio no son de mucha utilidad. Así que aquí está la solución que se me ocurrió:

public override void OnAuthorization(HttpActionContext actionContext) 
{ 
    string username; 
    string password; 

    if (GetUserNameAndPassword(actionContext, out username, out password)) 
    { 
     if (Membership.ValidateUser(username, password)) 
     { 
      if (!isUserAuthorized(username)) 
       actionContext.Response = 
        new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden); 
     } 
     else 
     { 
      actionContext.Response = 
       new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); 
     } 
    } 
    else 
    { 
     actionContext.Response = 
      new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); 
    } 
} 

desarrollé mi propio método para la validación de los papeles llamados isUserAuthorized y no estoy usando la base OnAuthorization más ya que comprueba la corriente Principio para ver si está autenticado. IsAuthenticated solo permite obtener así que no estoy seguro de cómo configurarlo, y no parece necesitar el actual Principio. Probado esto y funciona bien.

Sigue interesado si alguien tiene una mejor solución o puede ver algún problema con este esta.

+0

¿Puede compartir el cuerpo del método para GetUserNameAndPassword (actionContext, out username, out password)? –

+3

@VijayBalkawade - Este código ahora es parte del proyecto de código abierto SimpleSecurity. Puede obtener el código fuente completo para GetUserNameAndPassword aquí. https://simplesecurity.codeplex.com/SourceControl/latest#SimpleSecurity.Filters/BasicAuthorizeAttribute.cs –

+0

Desde CodePlex está cerrando pronto, he [copiado el 'GetUserNameAndPassword' en un Pastebin] (https://pastebin.com/Yq86aNjL). –

23

Para agregar a la respuesta ya aceptada: Comprobando el código fuente actual (aspnetwebstack.codeplex.com) para System.Web.Http.AuthorizeAttribute, parece que la documentación está desactualizada. La base OnAuthorization() solo llama/verifica el estado estático privado SkipAuthorization() (que solo comprueba si AllowAnonymousAttribute se usa en contexto para eludir el resto de la verificación de autenticación). Luego, si no se omite, OnAuthorization() llama al público IsAuthorized() y si esa llamada falla, llama al protegido virtual HandleUnauthorizedRequest(). Y eso es todo lo que hace ...

public override void OnAuthorization(HttpActionContext actionContext) 
{ 
    if (actionContext == null) 
    { 
     throw Error.ArgumentNull("actionContext"); 
    } 

    if (SkipAuthorization(actionContext)) 
    { 
     return; 
    } 

    if (!IsAuthorized(actionContext)) 
    { 
     HandleUnauthorizedRequest(actionContext); 
    } 
} 

Mirando el interior IsAuthorized(), que es donde se comprueba con el Principio roles y usuarios. Por lo tanto, anular IsAuthorized() con lo que tiene arriba en lugar de OnAuthorization() sería el camino a seguir. Por otra parte, aún tendrá que anular OnAuthorization() o HandleUnauthorizedRequest() para decidir cuándo devolver una respuesta 401 frente a 403.

1

Para agregar a la respuesta absolutamente correcta de Kevin, me gustaría decir que puedo modificarla ligeramente para aprovechar la ruta existente de .NET Framework para el objeto de respuesta para asegurar el código descendente en el marco (u otros consumidores) no se ve negativamente afectado por una idiosincrasia extraña que no puede predecirse.

Específicamente Esto significa utilizar este código:

actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, REQUEST_NOT_AUTHORIZED); 

en lugar de:

actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); 

Dónde REQUEST_NOT_AUTHORIZED es:

private const string REQUEST_NOT_AUTHORIZED = "Authorization has been denied for this request."; 

Saqué que string de la definición SRResources.RequestNotAuthorized en el. NET Framework.

Gran respuesta Kevin! He implementado mina de la misma manera porque la ejecución de OnAuthorization en la clase base no tenía sentido porque estaba verificando un encabezado HTTP que era costumbre de nuestra aplicación y en realidad no desea comprobar el director en absoluto, porque no había uno.