Pourquoi AuthorizeAttribute redirige-t-il vers la page de connexion pour les échecs d'authentification et d'autorisation?

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

Question

Dans ASP.NET MVC, vous pouvez marquer une méthode de contrôleur avec AuthorizeAttribute , comme ceci:

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

Cela signifie que, si l'utilisateur actuellement connecté n'est pas dans le champ "CanDeleteTags". rôle, la méthode du contrôleur ne sera jamais appelée.

Malheureusement, en cas d'échec, AuthorizeAttribute renvoie HttpUnauthorizedResult , qui renvoie toujours le code d'état HTTP 401. Cela entraîne une redirection vers la page de connexion.

Si l'utilisateur n'est pas connecté, cela est parfaitement logique. Cependant, si l'utilisateur est déjà connecté, mais ne possède pas le rôle requis, il est difficile de le renvoyer à la page de connexion.

Il semble que AuthorizeAttribute confond l'authentification et l'autorisation.

Cela semble être un oubli dans ASP.NET MVC, ou est-ce que je manque quelque chose?

J'ai dû créer un DemandRoleAttribute qui sépare les deux. Lorsque l'utilisateur n'est pas authentifié, il renvoie HTTP 401, en l'envoyant à la page de connexion. Lorsque l'utilisateur est connecté mais qu'il ne possède pas le rôle requis, il crée plutôt un NotAuthorizedResult . Actuellement, cela redirige vers une page d'erreur.

Je n'ai sûrement pas eu à faire ça?

Était-ce utile?

La solution

Quand il a été développé pour la première fois, System.Web.Mvc.AuthorizeAttribute faisait le bon choix - Les anciennes révisions de la spécification HTTP utilisaient le code d'état 401 à la fois "non autorisé" et "non autorisé". et "non authentifié".

D'après les spécifications d'origine:

  

Si la demande contient déjà les informations d'identification de l'autorisation, la réponse 401 indique que l'autorisation a été refusée pour ces informations.

En fait, vous pouvez voir la confusion ici: le mot "autorisation" est utilisé. quand cela signifie "authentification". Dans la pratique courante, toutefois, il est plus logique de renvoyer un message 403 Forbidden lorsque l'utilisateur est authentifié mais non autorisé. Il est peu probable que l'utilisateur dispose d'un second jeu d'informations d'identification qui lui donnerait accès: une mauvaise expérience utilisateur dans tous les domaines.

Pensez à la plupart des systèmes d'exploitation: lorsque vous essayez de lire un fichier que vous n'avez pas la permission d'accéder, aucun écran de connexion ne vous est montré!

Heureusement, les spécifications HTTP ont été mises à jour (juin 2014) pour éliminer toute ambiguïté.

À partir de " Protocole de transport Hyper Text (HTTP / 1.1): Authentification " (RFC 7235):

  

Le code d'état 401 (non autorisé) indique que la demande n'a pas été appliquée car elle ne dispose pas d'informations d'authentification valides pour la ressource cible.

Extrait de "Protocole de transfert hypertexte (HTTP / 1.1): sémantique et contenu" (RFC 7231):

  

Le code d'état 403 (interdit) indique que le serveur a compris la requête mais refuse de l'autoriser.

Fait intéressant, au moment de la publication d'ASP.NET MVC 1, le comportement de AuthorizeAttribute était correct. Maintenant, le comportement est incorrect - la spécification HTTP / 1.1 a été corrigée.

Plutôt que d'essayer de modifier les redirections de la page de connexion d'ASP.NET, il est plus simple de résoudre le problème à la source. Vous pouvez créer un nouvel attribut avec le même nom ( AuthorizeAttribute ) dans l'espace de noms par défaut de votre site Web (très important). Le compilateur le récupèrera automatiquement au lieu du nom standard de MVC. un. Bien sûr, vous pouvez toujours attribuer un nouveau nom à l'attribut si vous préférez adopter cette approche.

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

Autres conseils

Ajoutez ceci à votre fonction Page_Load de connexion:

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

Lorsque l'utilisateur y est redirigé mais qu'il est déjà connecté, la page non autorisée s'affiche. S'ils ne sont pas connectés, la page de connexion s'affiche.

J'ai toujours pensé que cela avait du sens. Si vous êtes connecté et que vous essayez d'accéder à une page nécessitant un rôle que vous n'avez pas, vous êtes redirigé vers l'écran de connexion, vous demandant de vous connecter avec un utilisateur possédant le rôle.

Vous pouvez ajouter une logique à la page de connexion pour vérifier si l'utilisateur est déjà authentifié. Vous pouvez ajouter un message amical expliquant pourquoi ils ont été ramassés là-bas.

Malheureusement, vous avez affaire au comportement par défaut de l'authentification par formulaire ASP.NET. Il existe une solution de contournement (je ne l'ai pas essayée) abordée ici:

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

(Ce n'est pas spécifique à MVC)

Je pense que dans la plupart des cas, la meilleure solution consiste à limiter l'accès aux ressources non autorisées avant que l'utilisateur ne souhaite y accéder. En supprimant / grisant le lien ou le bouton qui pourrait les amener à cette page non autorisée.

Il serait probablement intéressant d’avoir un paramètre supplémentaire sur l’attribut pour spécifier où rediriger un utilisateur non autorisé. Mais dans l’intervalle, je considère AuthorizeAttribute comme un filet de sécurité.

Essayez ceci dans le gestionnaire Application_EndRequest de votre fichier 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 vous utilisez aspnetcore 2.0, utilisez ceci:

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

Dans mon cas, le problème était "code de statut 401 utilisé par la spécification HTTP pour les deux" non autorisé ". et "non authentifié". Comme dit ShadowChaser.

Cette solution fonctionne pour moi:

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" });
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top