Autenticación y autorización en ASP.NET API web

de Rick Anderson

Ha creado una API web, pero ahora quiere controlar el acceso a ella. En esta serie de artículos, veremos algunas opciones para proteger una API web de usuarios no autorizados. Esta serie abarcará tanto la autenticación como la autorización.

  • La autenticación consiste en conocer la identidad del usuario. Por ejemplo, Alice inicia sesión con su nombre de usuario y contraseña, y el servidor usa la contraseña para autenticar a Alice.
  • La autorización decide si un usuario puede realizar una acción. Por ejemplo, Alice tiene permiso para obtener un recurso, pero no crear un recurso.

En el primer artículo de la serie se proporciona información general sobre la autenticación y autorización en ASP.NET API web. Otros temas describen escenarios de autenticación comunes para la API web.

Nota:

Gracias a las personas que revisaron esta serie y proporcionaron comentarios valiosos: Rick Anderson, Levi Broderick, Barry Dorrans, Tom Dykstra, Hongmei Ge, David Matson, Daniel Roth, Tim Teebken.

Autenticación

La API web supone que la autenticación se produce en el host. Para el hospedaje web, el host es IIS, que usa módulos HTTP para la autenticación. Puede configurar el proyecto para que use cualquiera de los módulos de autenticación integrados en IIS o ASP.NET, o escribir su propio módulo HTTP para realizar la autenticación personalizada.

Cuando el host autentica al usuario, crea un principal, que es un objeto IPrincipal que representa el contexto de seguridad en el que se ejecuta el código. El host adjunta la entidad de seguridad al subproceso actual estableciendo Thread.CurrentPrincipal. La principal de seguridad contiene un objeto Identity asociado que contiene información sobre el usuario. Si el usuario está autenticado, la propiedad Identity.IsAuthenticated devuelve true. En el caso de las solicitudes anónimas, IsAuthenticated devuelve false. Para obtener más información sobre los principales, consulte Role-Based Security.

Controladores de mensajes HTTP para la autenticación

En lugar de usar el host para la autenticación, puede colocar la lógica de autenticación en un controlador de mensajes HTTP. En ese caso, el controlador de mensajes examina la solicitud HTTP y establece el principal.

¿Cuándo debe usar controladores de mensajes para la autenticación? Estos son algunos inconvenientes:

  • Un módulo HTTP ve todas las solicitudes que pasan por la canalización de ASP.NET. Un controlador de mensajes solo ve las solicitudes que se enrutan a la API Web.
  • Puede establecer controladores de mensajes por ruta, lo que le permite aplicar un esquema de autenticación a una ruta específica.
  • Los módulos HTTP son específicos de IIS. Los controladores de mensajes son independientes del host, por lo que se pueden usar tanto con hospedaje web como con autohospedaje.
  • Los módulos HTTP participan en el registro, la auditoría, etc. de IIS.
  • Los módulos HTTP se ejecutan antes en la canalización. Si controla la autenticación en un controlador de mensajes, el principal no se establece hasta que funciona el controlador. Además, el principal de seguridad vuelve al principal de seguridad anterior cuando la respuesta sale del controlador de mensajes.

Por lo general, si no es necesario admitir el autohospedaje, un módulo HTTP es una mejor opción. Si necesita admitir el autohospedaje, considere la posibilidad de usar un controlador de mensajes.

Configuración del Principal

Si su aplicación realiza cualquier lógica de autenticación personalizada, debe establecer el principal de seguridad en dos lugares:

  • Thread.CurrentPrincipal. Esta propiedad es la manera estándar de establecer la entidad de seguridad del subproceso en .NET.
  • HttpContext.Current.User. Esta propiedad es específica de ASP.NET.

El código siguiente muestra cómo establecer el usuario principal:

private void SetPrincipal(IPrincipal principal)
{
    Thread.CurrentPrincipal = principal;
    if (HttpContext.Current != null)
    {
        HttpContext.Current.User = principal;
    }
}

Para el hospedaje web, debe establecer el principal en ambos lugares; de lo contrario, el contexto de seguridad puede volverse incoherente. Sin embargo, para el autohospedaje, HttpContext.Current es null. Para asegurarse de que el código es independiente del host, compruebe si hay valores NULL antes de asignarlo a HttpContext.Current, como se muestra.

Authorization

La autorización se produce más adelante en la canalización, más cerca del controlador. Esto le permite tomar decisiones más pormenorizadas al conceder acceso a los recursos.

  • Los filtros de autorización se ejecutan antes de la acción del controlador. Si la solicitud no está autorizada, el filtro devuelve una respuesta de error y la acción no se invoca.
  • Dentro de una acción de controlador, puede obtener el principal actual de la propiedad ApiController.User. Por ejemplo, podría filtrar una lista de recursos en función del nombre de usuario, devolviendo solo los recursos que pertenecen a ese usuario.

Diagrama de la canalización de autenticación y autorización.

Uso del atributo [Authorize]

La API web proporciona un filtro de autorización integrado , AuthorizeAttribute. Este filtro comprueba si el usuario está autenticado. Si no es así, devuelve el código de estado HTTP 401 (no autorizado), sin invocar la acción.

Puede aplicar el filtro globalmente, en el nivel de controlador o en el nivel de acciones individuales.

Globalmente: para restringir el acceso a cada controlador de API web, agregue el filtro AuthorizeAttribute a la lista de filtros global:

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

Controlador: para restringir el acceso a un controlador específico, agregue el filtro como atributo al controlador:

// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

Acción: para restringir el acceso a acciones específicas, agregue el atributo al método de acción:

public class ValuesController : ApiController
{
    public HttpResponseMessage Get() { ... }

    // Require authorization for a specific action.
    [Authorize]
    public HttpResponseMessage Post() { ... }
}

Como alternativa, puede restringir el controlador y, a continuación, permitir el acceso anónimo a acciones específicas mediante el [AllowAnonymous] atributo . En el ejemplo siguiente, el Post método está restringido, pero el Get método permite el acceso anónimo.

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get() { ... }

    public HttpResponseMessage Post() { ... }
}

En los ejemplos anteriores, el filtro permite que cualquier usuario autenticado acceda a los métodos restringidos; solo se mantienen fuera los usuarios anónimos. También puede limitar el acceso a usuarios específicos o a usuarios en roles específicos:

// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
   
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}

Nota:

El filtro AuthorizeAttribute para controladores de API web se encuentra en el espacio de nombres System.Web.Http . Hay un filtro similar para los controladores MVC en el espacio de nombres System.Web.Mvc , que no es compatible con controladores de API web.

Filtros de autorización personalizados

Para escribir un filtro de autorización personalizado, derive de uno de estos tipos:

  • AuthorizeAttribute. Extienda esta clase para realizar la lógica de autorización en función del usuario actual y de los roles del usuario.
  • AuthorizationFilterAttribute. Extienda esta clase para realizar lógica de autorización sincrónica que no se basa necesariamente en el usuario o rol actual.
  • IAuthorizationFilter. Implemente esta interfaz para realizar lógica de autorización asincrónica; Por ejemplo, si la lógica de autorización realiza llamadas asincrónicas de E/S o de red. (Si la lógica de autorización está enlazada a cpu, es más fácil derivar de AuthorizationFilterAttribute, ya que no es necesario escribir un método asincrónico).

En el diagrama siguiente se muestra la jerarquía de clases de la clase AuthorizeAttribute .

Diagrama de la jerarquía de clases para la clase Authorize Attribute.

Diagrama de la jerarquía de clases para la clase Authorize Attribute. Authorize Attribute está en la parte inferior, con una flecha que apunta a Authorization Filter Attribute (Atributo de filtro de autorización) y una flecha que apunta a I Authorization Filter (Filtro de autorización I) en la parte superior.

Autorización dentro de una acción del controlador

En algunos casos, puede permitir que una solicitud continúe, pero cambie el comportamiento en función del principal. Por ejemplo, la información que devuelve podría cambiar en función del rol del usuario. Dentro de un método de controlador, puede obtener el principal actual de la propiedad ApiController.User.

public HttpResponseMessage Get()
{
    if (User.IsInRole("Administrators"))
    {
        // ...
    }
}