asp.net mvc - mvc - ¿Por qué AuthorizeAttribute redirige a la página de inicio de sesión por fallas de autenticación y autorización?
custom authorize attribute mvc 5 (6)
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 está en la función "CanDeleteTags", nunca se llamará al método del controlador.
Desafortunadamente, para los fallos, 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 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 NotAuthorizedResult
lugar. Actualmente esto redirige a una página de error.
Seguramente no tuve que hacer esto?
Agregue esto a su función Login Page_Load:
// 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 están conectados, se muestra y muestra la página de inicio de sesión.
Cuando se desarrolló por primera vez, System.Web.Mvc.AuthorizeAttribute estaba haciendo lo correcto: las revisiones más antiguas de la especificación HTTP utilizaban el código de estado 401 para "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: una mala experiencia para el usuario.
Considere la mayoría de los sistemas operativos: cuando intenta leer un archivo al que no tiene permiso para acceder, ¡no se le muestra 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. Por supuesto, siempre podría darle al atributo un nombre nuevo 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);
}
}
}
Desafortunadamente, 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.
Intente esto en su en el controlador Application_EndRequest de su 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;
}
}
}
}
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í.