web-dev-qa-db-fra.com

ASP.NET MVC - Comment afficher une erreur non autorisée sur la page de connexion?

Dans mon application ASP.NET MVC, la plupart des contrôleurs sont décorés de

[Authorize(Roles="SomeGroup")]

Lorsqu'un utilisateur n'est pas autorisé à accéder à quelque chose, il est envoyé à "~/Login" qui est l'action de connexion sur mon contrôleur de compte.

Comment puis-je déterminer qu'un utilisateur a atteint la page de connexion parce qu'il n'est pas autorisé afin que je puisse afficher une erreur appropriée?

45
Ronnie Overby

Vous pouvez rechercher le ?ReturnUrl= querystring value, ou vous pouvez créer votre propre filtre d'autorisation et définir un champ dans TempData indiquant la raison.

Voici un simple filtre personnalisé qui fera l'affaire:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{

    // NOTE: This is not thread safe, it is much better to store this
    // value in HttpContext.Items.  See Ben Cull's answer below for an example.
    private bool _isAuthorized;

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        _isAuthorized = base.AuthorizeCore(httpContext);
        return _isAuthorized;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        if(!_isAuthorized)
        {
            filterContext.Controller.TempData.Add("RedirectReason", "Unauthorized");
        }
    }
}

Ensuite, selon vous, vous pouvez faire quelque chose comme ceci:

@if(TempData["RedirectReason"] == "Unauthorized")
{
    <b>You don't have permission to access that area</b>
}

(Bien que je recommanderais une meilleure approche que ces cordes magiques, mais vous obtenez le point)

28
Ben Scheirman

MISE À JOUR (juin 2015): @ daniel-lidström a correctement indiqué que vous ne devez pas utiliser Response.Redirect dans une application ASP.NET MVC. Pour plus d'informations sur pourquoi, veuillez consulter ce lien: Response.Redirect et ASP.NET MVC - Ne pas mélanger .

MISE À JOUR (septembre 2014): Je ne sais pas quand HandleUnauthorizedRequest a été ajouté à AuthorizeAttribute, mais de toute façon j'ai pu affiner le code AuthorizeRedirect en quelque chose de plus petit et plus simple.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
    public string RedirectUrl = "~/Error/Unauthorized";

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new RedirectResult(RedirectUrl);
        }
    }
}

Réponse originale ci-dessous (toujours entièrement fonctionnelle)

J'ai laissé cette réponse ici car elle vous donne toujours un aperçu du fonctionnement du pipeline d'autorisation.

Pour tous ceux qui atterrissent encore ici, j'ai modifié la réponse de Ben Scheirman pour rediriger automatiquement vers une page non autorisée lorsque l'utilisateur est connecté mais non autorisé. Vous pouvez modifier le chemin de redirection à l'aide du paramètre de nom RedirectUrl.

EDIT: J'ai rendu la solution thread-safe grâce aux conseils de Tarynn et MSDN

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
    private const string IS_AUTHORIZED = "isAuthorized";

    public string RedirectUrl = "~/error/unauthorized";

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        bool isAuthorized = base.AuthorizeCore(httpContext);

        httpContext.Items.Add(IS_AUTHORIZED, isAuthorized);

        return isAuthorized;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        var isAuthorized = filterContext.HttpContext.Items[IS_AUTHORIZED] != null 
            ? Convert.ToBoolean(filterContext.HttpContext.Items[IS_AUTHORIZED]) 
            : false;

        if (!isAuthorized && filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl);
        }
    }
}
76
Ben Cull

La méthode de Ben Cull fonctionne bien, mais n'oubliez pas qu'il existe deux classes AuthorizeAttribute - l'une dans System.Web.HTTP (utilisée par l'API Web) et l'autre dans System.Web.Mvc. La méthode de Ben utilise la classe System.Web.Mvc. Pour plus de clarté, je suggère d'utiliser le chemin d'accès complet.

Si vous utilisez l'API Web avec MVC, vous devrez implémenter deux filtres:

public class AuthorizeRedirectMVCAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new RedirectResult("~/Account/AccessDenied");
        }
    }
}

public class AuthorizeRedirectAPIAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);

        if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
        }
    }
}

Notez que asp.net vous permettra de décorer votre contrôleur MVC avec un filtre API - cela ne fonctionnera tout simplement pas comme vous l'attendez, alors gardez vos noms d'attribut explicites.

5
Brian Vander Plaats

Si vous avez un contrôleur et que vous ne voulez pas avoir d'URL dans votre code, vous pouvez également rediriger de cette façon. Il ne changera pas l'URL dans la barre d'adresse du navigateur, donc l'utilisateur ne verra jamais l'URL de la page non autorisée. Cela a été écrit dans MVC 3. Cette méthode fonctionnera également si vous souhaitez les rediriger vers une page de connexion ou si vous souhaitez les rediriger vers une page pour leur dire simplement qu'ils ne sont pas autorisés. J'avais une section dans le programme à laquelle certains utilisateurs n'avaient pas de droits mais ils étaient connectés, c'est donc ce que j'ai utilisé.

public class AuthorizedRedirect : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool isAuthorized = base.AuthorizeCore(httpContext);
        return isAuthorized;
    }
protect override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    filterContext.RequestContext.RouteData.Values["controller"] = "error";
    filterContext.Result = new ViewResult { ViewName = "unauthorized" };
}
4
divide_byzero

Et une version encore plus simple qui utilise les paramètres FormsAuthentication. Pour ceux qui ne connaissent pas Contract, Contract.Requires est un ajout .NET 4. Avantages et inconvénients de l'utilisation de Contrats de code .

public class RequiresAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        Contract.Requires(filterContext != null);

        HttpContextBase context = filterContext.RequestContext.HttpContext;

        if (context.User.Identity.IsAuthenticated)
        {
            // user does not possess the required role permission
            string url = context.GetCustomErrorUrl(401);
            context.Response.Redirect(url);
        }
        else
        {

            // redirect the user to the login page
            string extraQueryString  = context.Request.RawUrl;
            FormsAuthentication.RedirectToLoginPage(extraQueryString);
        }
    }
}
2
mousedoc

En allant plus loin de la réponse de divide_byzero même si vous n'avez pas de contrôleur, vous pouvez toujours utiliser HandleUnauthorizedRequest pour modifier la redirection.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AuthoriseRedirect : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.RequestContext.HttpContext.Response.Redirect("UrlToRedirectTo");
        }
    }

Est utile si vous avez un site Webforms hérité que vous allez convertir en MVC sur une plus longue période de temps .....!

1
Bindi

J'aime ce que Brian Vander Plaats a publié, juste quelques améliorations:

/// <summary>
/// Authorize or redirect to an unauthorized MVC action if the user does not have the required roles
/// (an unauthenticated user will be redirected to the defualt sign in action)
/// <para>Decorate an action or a controller like this [AuthorizeAndRedirect(Roles = "RoleName")]</para>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthorizeOrRedirectAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", "Error");
            routeData.Values.Add("action", "Unauthorized");
            filterContext.Result = new RedirectToRouteResult(routeData.Values);
        }
    }
}

/// <summary>
/// Authorize or redirect to an unauthorized API action if the user does not have the required roles
/// (an unauthenticated user will be redirected to the defualt sign in action)
/// <para>Decorate an action or a controller like this [AuthorizeAndRedirect(Roles = "RoleName")]</para>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthorizeOrRedirectApiFilterAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);

        if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }
}
0
Yovav