Por que o AuthorizeAttribute redireciona para a página de login para falhas de autenticação e autorização?

No asp.net MVC, você pode marcar um método de controlador com AuthorizeAttribute , como este:

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

Isso significa que, se o usuário conectado no momento não estiver na function “CanDeleteTags”, o método do controlador nunca será chamado.

Infelizmente, para falhas, o AuthorizeAttribute retorna HttpUnauthorizedResult , que sempre retorna o código de status HTTP 401. Isso causa um redirecionamento para a página de login.

Se o usuário não estiver logado, isso faz todo o sentido. No entanto, se o usuário estiver conectado, mas não estiver na function exigida, será confuso enviá-lo de volta à página de login.

Parece que AuthorizeAttribute conflates autenticação e autorização.

Isso parece um pouco de descuido no asp.net MVC, ou estou faltando alguma coisa?

Eu tive que cozinhar um DemandRoleAttribute que separa os dois. Quando o usuário não é autenticado, ele retorna o HTTP 401, enviando-o para a página de login. Quando o usuário está logado, mas não está na function requerida, ele cria um NotAuthorizedResult . Atualmente, isso redireciona para uma página de erro.

Certamente eu não tenho que fazer isso?

   

    Quando foi desenvolvido pela primeira vez, o System.Web.Mvc.AuthorizeAttribute estava fazendo a coisa certa – as revisões mais antigas da especificação HTTP usavam o código de status 401 para “não autorizado” e “não autenticado”.

    Da especificação original:

    Se a solicitação já incluiu credenciais de autorização, a resposta 401 indicará que a autorização foi recusada para essas credenciais.

    De fato, você pode ver a confusão ali mesmo – ela usa a palavra “autorização” quando significa “autenticação”. Na prática cotidiana, no entanto, faz mais sentido retornar um 403 Proibido quando o usuário é autenticado, mas não autorizado. É improvável que o usuário tenha um segundo conjunto de credenciais que lhes proporcione access – experiência ruim do usuário por toda parte.

    Considere a maioria dos sistemas operacionais – quando você tenta ler um arquivo que você não tem permissão para acessar, você não é mostrado uma canvas de login!

    Felizmente, as especificações HTTP foram atualizadas (junho de 2014) para remover a ambigüidade.

    De “Hyper Text Transport Protocol (HTTP / 1.1): Autenticação” (RFC 7235):

    O código de status 401 (não autorizado) indica que a solicitação não foi aplicada porque não possui credenciais de autenticação válidas para o recurso de destino.

    De “Protocolo de Transferência de Hipertexto (HTTP / 1.1): Semântica e Conteúdo” (RFC 7231):

    O código de status 403 (Proibido) indica que o servidor entendeu a solicitação, mas se recusou a autorizá-la.

    Curiosamente, no momento em que a ASP.NET MVC 1 foi lançada, o comportamento de AuthorizeAttribute estava correto. Agora, o comportamento está incorreto – a especificação HTTP / 1.1 foi corrigida.

    Em vez de tentar alterar os redirecionamentos da página de login do ASP.NET, é mais fácil corrigir o problema na origem. Você pode criar um novo atributo com o mesmo nome ( AuthorizeAttribute ) no namespace padrão do seu site (isso é muito importante), então o compilador irá buscá-lo automaticamente em vez do padrão do MVC. Claro, você sempre pode dar ao atributo um novo nome se preferir adotar essa abordagem.

     [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); } } } 

    Adicione isto à sua function de Login Page_Load:

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

    Quando o usuário é redirecionado, mas já está logado, ele mostra a página não autorizada. Se eles não estiverem logados, ele passará e mostrará a página de login.

    Eu sempre achei que isso fazia sentido. Se você está logado e tenta acessar uma página que requer uma function que você não tem, você é encaminhado para a canvas de login solicitando que você faça login com um usuário que tenha a function.

    Você pode adicionar lógica à página de login que verifica se o usuário já está autenticado. Você poderia adicionar uma mensagem amigável que explica por que eles foram bumbed lá novamente.

    Infelizmente, você está lidando com o comportamento padrão da autenticação de formulários do ASP.NET. Existe uma solução alternativa (não tentei) discutida aqui:

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

    (Não é específico para MVC)

    Na maioria dos casos, acho que a melhor solução é restringir o access a resources não autorizados antes que o usuário tente chegar lá. Removendo / diminuindo o link ou botão que pode levá-los a essa página não autorizada.

    Provavelmente seria bom ter um parâmetro adicional no atributo para especificar onde redirect um usuário não autorizado. Mas, enquanto isso, vejo o AuthorizeAttribute como uma rede de segurança.

    Tente isso no seu no manipulador Application_EndRequest do seu arquivo Global.ascx

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

    Se você estiver usando o aspnetcore 2.0, use isto:

     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; } } } }