2008-10-26 11 views
234

En ASP.NET MVC, puede marcar un método controlador con AuthorizeAttribute, así:¿Por qué AuthorizeAttribute redirige a la página de inicio de sesión para las fallas de autenticación y autorización?

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

Esto significa que, si ha iniciado la sesión en el usuario no está en el papel "CanDeleteTags", el controlador método nunca será llamado.

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

Si el usuario no ha iniciado sesión, tiene mucho sentido. Sin embargo, si el usuario está ya registrado en, pero no está en la función requerida, es confuso enviarlos de nuevo a la página de inicio de sesión.

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

Esto parece un poco un descuido en ASP.NET MVC, o me falta algo?

He tenido que cocinar un DemandRoleAttribute que separa los dos. Cuando el usuario no está autenticado, devuelve HTTP 401 y lo envía a la página de inicio de sesión. Cuando el usuario inicia sesión, pero no está en la función requerida, crea un NotAuthorizedResult en su lugar. Actualmente esto redirige a una página de error.

Seguramente no tuve que hacer esto?

+7

Excelente pregunta y estoy de acuerdo, debería arrojar un estado de HTTP no autorizado. –

+2

Me gusta su solución, Roger. Incluso si no lo haces. –

+0

Mi página de inicio de sesión tiene un cheque para simplemente redirigir al usuario a ReturnUrl, si ya está autenticado. Así que logré crear un bucle infinito de 302 redirecciones: D woot. –

Respuesta

282

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

de las especificaciones originales:

If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials.

De hecho, se puede ver la confusión allí - que utiliza la palabra "autorización" cuando significa "autenticación". En la práctica diaria, sin embargo, tiene más sentido devolver un 403 Forbidden cuando el usuario está autenticado pero no autorizado. Es poco probable que el usuario tenga un segundo conjunto de credenciales que les daría 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 de acceso, ¡no se muestra una pantalla de inicio de sesión!

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

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

The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource.

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

The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it.

Curiosamente, en el momento ASP.NET MVC 1 fue lanzado el comportamiento de AuthorizeAttribute era correcta. Ahora, el comportamiento es incorrecto: se corrigió la especificación HTTP/1.1.

En lugar de intentar cambiar los redireccionamientos de la página de inicio de sesión de ASP.NET, es más fácil simplemente solucionar el problema en el origen. Puede crear un nuevo atributo con el mismo nombre (AuthorizeAttribute) en el espacio de nombre 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 puedes darle un nuevo nombre al atributo si prefieres 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); 
     } 
    } 
} 
+48

+1 Muy buen enfoque. Una pequeña sugerencia: en lugar de verificar 'filterContext.HttpContext.User.Identity.IsAuthenticated', puedes simplemente verificar' filterContext.HttpContext.Request.IsAuthenticated', que viene con verificaciones nulas incorporadas. Ver http://stackoverflow.com/ questions/1379566/what-is-the-difference-between-httpcontext-current-request-isauthenticated-and-ht/1379601 # 1379601 –

+5

Excelente idea: he actualizado la respuesta. – ShadowChaser

+0

> Puede crear un nuevo atributo con el mismo nombre (AuthorizeAttribute) en el espacio de nombres predeterminado de su sitio web, luego el compilador lo recogerá automáticamente en lugar del estándar de MVC. Esto da como resultado un error: No se pudo encontrar el tipo o el espacio de nombres 'Autorizar' (¿falta una directiva o una referencia de ensamblado?) Ambos utilizando System.Web.Mvc; y el espacio de nombre para mi clase personalizada AuthorizeAttribute se referencia en el controlador. Para resolver esto tuve que usar [MyNamepace.Authorize] – stormwild

4

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

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

(No es específico para MVC)

Creo que en la mayoría de los casos, la mejor solución es restringir el acceso a los recursos no autorizadas antes de que el usuario intente llegar allí. Al eliminar/encadenar el enlace o botón que podría 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, miro el AuthorizeAttribute como una red de seguridad.

+0

También planeo eliminar el enlace basado en la autorización (vi una pregunta aquí acerca de eso en alguna parte), así que codificaré un método de extensión HtmlHelper más adelante. –

+1

Todavía tengo que evitar que el usuario vaya directamente a la URL, que es de lo que se trata este atributo. No estoy muy contento con la solución Custom 401 (parece un poco global), así que intentaré modelar mi NotAuthorizedResult en RedirectToRouteResult ... –

4

Siempre pensé que esto tenía sentido. Si ha iniciado sesión y trata de acceder a una página que requiere un rol que no tiene, se lo reenvía a la pantalla de inicio de sesión solicitándole que inicie sesión con un usuario que sí tiene el rol.

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

+4

Tengo la sensación de que la mayoría de las personas no tiende a tener más de una identidad para un aplicación web dada. Si lo hacen, son lo suficientemente inteligentes como para pensar "mi ID actual no tiene mojo, volveré a iniciar sesión como la otra". –

+0

Aunque su otro punto acerca de mostrar algo en la página de inicio de sesión es bueno. Gracias. –

23

Agregue esto a su función Load ingreso:

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

Cuando el usuario es redirigido allí, pero ya se ha iniciado sesión, se muestra la página no autorizado. Si no están conectados, se cae y muestra la página de inicio de sesión.

+17

Page_Load es un formulario web mojo – Chance

+2

@Chance - luego hazlo en el ActionMethod predeterminado para el controlador que se llama donde FormsAuthencation se ha configurado para llamar. –

+0

Aunque esto realmente funciona muy bien para MVC, debería ser algo así como 'if (User.Identity! = Null && User.Identity.IsAuthenticated) return RedirectToRoute (" Unauthorized ");' donde * Unauthorized * es un nombre de ruta definido. –

-1

Pruebe esto en su en el controlador Application_EndRequest de su Global.ascx presentar

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

Si su uso aspnetcore 2.0, utilice 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; 
      } 
     } 
    } 
} 
Cuestiones relacionadas