¿Por qué AuthorizeAttribute redirige a la página de inicio de sesión para fallas de autenticación y autorización?

StackOverflow https://stackoverflow.com/questions/238437

Pregunta

En ASP.NET MVC, puede marcar un método de controlador con AuthorizeAttribute , como esto:

[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
    // ...
}

Esto significa que, si el usuario que ha iniciado sesión actualmente no se encuentra en " CanDeleteTags " rol, el método del controlador nunca será llamado.

Desafortunadamente, para las fallas, AuthorizeAttribute devuelve HttpUnauthorizedResult , que siempre devuelve el código de estado HTTP 401. Esto provoca una redirección a la página de inicio de sesión.

Si el usuario no ha iniciado sesión, esto tiene mucho sentido. Sin embargo, si el usuario ya ha iniciado sesión, pero no está en el rol requerido, es confuso enviarlo de vuelta a la página de inicio de sesión.

Parece que AuthorizeAttribute combina la autenticación y la autorización.

Esto parece ser un poco descuidado en ASP.NET MVC, ¿o me estoy perdiendo algo?

He tenido que preparar un DemandRoleAttribute que separa a los dos. Cuando el usuario no está autenticado, devuelve HTTP 401, enviándolo a la página de inicio de sesión. Cuando el usuario ha iniciado sesión, pero no está en el rol requerido, crea un NotAuthorizedResult en su lugar. Actualmente esto redirige a una página de error.

Seguramente no tuve que hacer esto?

¿Fue útil?

Solución

Cuando se desarrolló por primera vez, System.Web.Mvc.AuthorizeAttribute estaba haciendo lo correcto - las revisiones anteriores de la especificación HTTP utilizaron el código de estado 401 para ambos " no autorizado " y " no autenticado " ;.

De la especificación original:

  

Si la solicitud ya incluía credenciales de autorización, entonces la respuesta 401 indica que se ha rechazado la autorización para esas credenciales.

De hecho, puede ver la confusión allí mismo: usa la palabra " autorización " cuando significa " autenticación " ;. En la práctica diaria, sin embargo, tiene más sentido devolver un 403 Prohibido cuando el usuario está autenticado pero no autorizado. Es poco probable que el usuario tenga un segundo conjunto de credenciales que les brinde acceso: la experiencia del usuario es mala.

Considere la mayoría de los sistemas operativos: cuando intenta leer un archivo al que no tiene permiso para acceder, ¡no aparece una pantalla de inicio de sesión!

Afortunadamente, las especificaciones HTTP se actualizaron (junio de 2014) para eliminar la ambigüedad.

Desde " Protocolo de transporte de hipertexto (HTTP / 1.1): Autenticación " (RFC 7235):

  

El código de estado 401 (no autorizado) indica que la solicitud no se ha aplicado porque carece de credenciales de autenticación válidas para el recurso de destino.

De " Protocolo de transferencia de hipertexto (HTTP / 1.1): Semántica y contenido " (RFC 7231):

  

El código de estado 403 (Prohibido) indica que el servidor entendió la solicitud, pero se niega a autorizarla.

Curiosamente, en el momento en que ASP.NET MVC 1 se lanzó, el comportamiento de AuthorizeAttribute era correcto. Ahora, el comportamiento es incorrecto: la especificación HTTP / 1.1 fue corregida.

En lugar de intentar cambiar los redireccionamientos de la página de inicio de sesión de ASP.NET, es más fácil solucionar el problema desde el origen. Puede crear un nuevo atributo con el mismo nombre ( AuthorizeAttribute ) en el espacio de nombres predeterminado de su sitio web (esto es muy importante), luego el compilador lo recogerá automáticamente en lugar del estándar de MVC uno. Por supuesto, siempre podría darle un nuevo nombre al atributo si prefiere adoptar ese enfoque.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

Otros consejos

Agregue esto a su función de carga de página de inicio de sesión:

// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
    Response.Redirect("Unauthorized.aspx");

Cuando el usuario se redirige allí pero ya ha iniciado sesión, muestra la página no autorizada. Si no han iniciado sesión, aparece y muestra la página de inicio de sesión.

Siempre pensé que esto tenía sentido. Si ha iniciado sesión e intenta acceder a una página que requiere un rol que no tiene, se le reenviará a la pantalla de inicio de sesión y le pedirá que inicie sesión con un usuario que tenga el rol.

Puede agregar lógica a la página de inicio de sesión que verifica si el usuario ya está autenticado. Podrías agregar un mensaje amigable que explique por qué han vuelto a sonar allí.

Lamentablemente, se trata del comportamiento predeterminado de la autenticación de formularios ASP.NET. Hay una solución (no lo he probado) discutida aquí:

http://www.codeproject.com/KB/aspnet/Custon401Page.aspx

(No es específico de MVC)

Creo que, en la mayoría de los casos, la mejor solución es restringir el acceso a recursos no autorizados antes de que el usuario intente llegar allí. Al eliminar / mostrar el enlace o el botón que puede llevarlos a esta página no autorizada.

Probablemente sería bueno tener un parámetro adicional en el atributo para especificar dónde redirigir a un usuario no autorizado. Pero mientras tanto, veo el AuthorizeAttribute como una red de seguridad.

Prueba esto en tu en el controlador Application_EndRequest de tu archivo Global.ascx

if (HttpContext.Current.Response.Status.StartsWith("302") && HttpContext.Current.Request.Url.ToString().Contains("/<restricted_path>/"))
{
    HttpContext.Current.Response.ClearContent();
    Response.Redirect("~/AccessDenied.aspx");
}

Si estás usando aspnetcore 2.0, usa esto:

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Core
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthorizeApiAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.User;

            if (!user.Identity.IsAuthenticated)
            {
                context.Result = new UnauthorizedResult();
                return;
            }
        }
    }
}

En mi caso, el problema fue que la " especificación HTTP usó el código de estado 401 para ambos " no autorizado " y " no autenticado " " ;. Como dijo ShadowChaser.

Esta solución funciona para mí:

if (User != null &&  User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
    //Do whatever

    //In my case redirect to error page
    Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top