web-dev-qa-db-fra.com

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

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 rôle "CanDeleteTags", 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 provoque 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 qu'il 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 peu un oubli dans ASP.NET MVC, ou est-ce que je manque quelque chose?

J'ai dû préparer une 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 une variable NotAuthorizedResult. Actuellement, cela redirige vers une page d'erreur.

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

252
Roger Lipscombe

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

De la spécification d'origine:

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

En fait, vous pouvez voir la confusion ici: il utilise le mot "autorisation" quand il 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 tout autour.

Considérez la plupart des systèmes d'exploitation - lorsque vous essayez de lire un fichier auquel vous n'êtes pas autorisé, aucun écran de connexion ne vous est présenté! 

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

Dans "Protocole de transfert de données (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.

Dans "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 demande 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 nom par défaut de votre site Web (ceci est très important). Le compilateur le sélectionnera automatiquement au lieu de celui standard de MVC. 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);
        }
    }
}
293
ShadowChaser

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.

24
Alan Jackson

Malheureusement, vous avez affaire au comportement par défaut de l'authentification par formulaire ASP.NET. Il y a une solution de contournement (je ne l'ai pas essayée) discuté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 tente d'y accéder. En supprimant/grisant le lien ou le bouton qui pourrait les amener à cette page non autorisée. 

Ce serait probablement bien 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é.

4
Keltex

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 qui possède 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é déplacés là-bas.

4
Rob

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;
            }
        }
    }
}
0
Greg Gum

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");
}
0
Kareem Cambridge

Dans mon cas, le problème était "Le code d'état 401 utilisé par la spécification HTTP était à la fois" non autorisé "et" non authentifié "". Comme ShadowChaser a dit.

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" });
}
0
César León