web-dev-qa-db-fra.com

Redirection du contrôleur non autorisé dans ASP.NET MVC

J'ai un contrôleur dans ASP.NET MVC que je me suis limité au rôle d'administrateur:

[Authorize(Roles = "Admin")]
public class TestController : Controller
{
   ...

Si un utilisateur qui ne fait pas partie du rôle Admin accède à ce contrôleur, il est accueilli avec un écran vide.

Ce que je voudrais faire est de les rediriger vers View qui dit "vous devez être dans le rôle Admin pour pouvoir accéder à cette ressource".

Une façon de procéder est d’avoir une vérification de chaque méthode d’action sur IsUserInRole () et, si elle n’a pas de rôle, de renvoyer cette vue informative. Cependant, je devrais mettre cela dans chaque action qui casse le principal DRY) et est évidemment fastidieux à maintenir.

73
Guy

Créez un attribut d'autorisation personnalisé basé sur AuthorizeAttribute et substituez OnAuthorization pour effectuer le contrôle comme vous le souhaitez. Normalement, AuthorizeAttribute définira le résultat du filtre sur HttpUnauthorizedResult si le contrôle des autorisations échoue. Vous pourriez le faire définir à un ViewResult (de votre vue d'erreur) à la place.

[~ # ~] éditer [~ # ~] : J'ai quelques articles de blog qui vont plus en détail:

Exemple:

    [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
    public class MasterEventAuthorizationAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// The name of the master page or view to use when rendering the view on authorization failure.  Default
        /// is null, indicating to use the master page of the specified view.
        /// </summary>
        public virtual string MasterName { get; set; }

        /// <summary>
        /// The name of the view to render on authorization failure.  Default is "Error".
        /// </summary>
        public virtual string ViewName { get; set; }

        public MasterEventAuthorizationAttribute()
            : base()
        {
            this.ViewName = "Error";
        }

        protected void CacheValidateHandler( HttpContext context, object data, ref HttpValidationStatus validationStatus )
        {
            validationStatus = OnCacheAuthorization( new HttpContextWrapper( context ) );
        }

        public override void OnAuthorization( AuthorizationContext filterContext )
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException( "filterContext" );
            }

            if (AuthorizeCore( filterContext.HttpContext ))
            {
                SetCachePolicy( filterContext );
            }
            else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();
            }
            else if (filterContext.HttpContext.User.IsInRole( "SuperUser" ))
            {
                // is authenticated and is in the SuperUser role
                SetCachePolicy( filterContext );
            }
            else
            {
                ViewDataDictionary viewData = new ViewDataDictionary();
                viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
                filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
            }

        }

        protected void SetCachePolicy( AuthorizationContext filterContext )
        {
            // ** IMPORTANT **
            // Since we're performing authorization at the action level, the authorization code runs
            // after the output caching module. In the worst case this could allow an authorized user
            // to cause the page to be cached, then an unauthorized user would later be served the
            // cached page. We work around this by telling proxies not to cache the sensitive page,
            // then we hook our custom authorization code into the caching mechanism so that we have
            // the final say on whether a page should be served from the cache.
            HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) );
            cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */);
        }


    }
69
tvanfosson

Vous pouvez utiliser le paramètre écrasable HandleUnauthorizedRequest dans votre AuthorizeAttribute personnalisé

Comme ça:

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    // Returns HTTP 401 by default - see HttpUnauthorizedResult.cs.
    filterContext.Result = new RedirectToRouteResult(
    new RouteValueDictionary 
    {
        { "action", "YourActionName" },
        { "controller", "YourControllerName" },
        { "parameterName", "YourParameterValue" }
    });
}

Vous pouvez aussi faire quelque chose comme ça:

private class RedirectController : Controller
{
    public ActionResult RedirectToSomewhere()
    {
        return RedirectToAction("Action", "Controller");
    }
}

Vous pouvez maintenant l’utiliser dans votre méthode HandleUnauthorizedRequest de cette façon:

filterContext.Result = (new RedirectController()).RedirectToSomewhere();
25
Leniel Maccaferri

Le code de "tvanfosson" me donnait le message "Erreur lors de l'exécution de la requête enfant". J'ai modifié l'autorisation OnAuthorization comme ceci:

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

        if (!_isAuthorized)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("User") ||  filterContext.HttpContext.User.IsInRole("Manager"))
        {
            // is authenticated and is in one of the roles 
            SetCachePolicy(filterContext);
        }
        else
        {
            filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
            filterContext.Result = new RedirectResult("~/Error");
        }
    }

Cela fonctionne bien et je montre le TempData sur la page d'erreur. Merci à "tvanfosson" pour l'extrait de code. J'utilise l'authentification Windows et _isAuthorized n'est rien d'autre que HttpContext.User.Identity.IsAuthenticated ...

9
sajoshi

J'ai eu le même problème. Plutôt que de comprendre le code MVC, j'ai opté pour un piratage économique qui semble fonctionner. Dans ma classe Global.asax:

member x.Application_EndRequest() =
  if x.Response.StatusCode = 401 then 
      let redir = "?redirectUrl=" + Uri.EscapeDataString x.Request.Url.PathAndQuery
      if x.Request.Url.LocalPath.ToLowerInvariant().Contains("admin") then
          x.Response.Redirect("/Login/Admin/" + redir)
      else
          x.Response.Redirect("/Login/Login/" + redir)
5
MichaelGG

Ce problème me hante depuis quelques jours maintenant, alors en trouvant la réponse qui fonctionne de manière affirmative avec la réponse de tvanfosson ci-dessus, j'ai pensé qu'il serait utile de mettre l'accent sur la partie principale de la réponse et d'aborder certains problèmes connexes.

La réponse principale est la suivante: douce et simple:

filterContext.Result = new HttpUnauthorizedResult();

Dans mon cas, j'hérite d'un contrôleur de base, donc dans chaque contrôleur qui en hérite, je remplace OnAuthorize:

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    base.OnAuthorization(filterContext);
    YourAuth(filterContext); // do your own authorization logic here
}

Le problème était que, dans "YourAuth", j'avais essayé deux solutions qui, à mon avis, pourraient non seulement fonctionner mais également mettre immédiatement fin à la demande. Eh bien, ce n'est pas comme ça que ça marche. Alors tout d’abord, les deux choses qui ne fonctionnent PAS, de façon inattendue:

filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work!
FormsAuthentication.RedirectToLoginPage(); // doesn't work!

Non seulement ceux qui ne travaillent pas, ils ne mettent pas fin à la demande non plus. Ce qui signifie que:

if (!success) {
    filterContext.Result = new HttpUnauthorizedResult();
}
DoMoreStuffNowThatYouThinkYourAuthorized();

Eh bien, même avec la bonne réponse ci-dessus, le flux de la logique continue! Vous frapperez toujours DoMoreStuff ... dans OnAuthorize. Alors gardez cela à l'esprit (DoMore ... devrait être dans un autre donc).

Mais avec la bonne réponse, alors que le flux de logique OnAuthorize continue jusqu’à la fin, vous obtenez réellement ce que vous attendez: une redirection vers votre page de connexion (si vous en avez un défini dans Autodes Form dans votre configuration Web).

Mais de manière inattendue, 1) Response.Redirect ("/ Login") ne fonctionne pas: la méthode Action est toujours appelée et 2) FormsAuthentication.RedirectToLoginPage (); fait la même chose: la méthode Action est toujours appelée!

Ce qui me semble totalement faux, en particulier avec ce dernier: qui aurait pensé que FormsAuthentication.RedirectToLoginPage ne met pas fin à la requête, ou l’équivalent précédent de quoi filterContext.Result = new HttpUnauthorizedResult ()

2
Nicholas Petersen

J'aurais laissé cela comme un commentaire, mais il me faudrait plus de représentants. De toute façon, je voulais simplement dire à Nicholas Peterson que passer le deuxième argument à l'appel de redirection pour lui dire de mettre fin à la réponse aurait peut-être fonctionné. Ce n’est pas la façon la plus gracieuse de gérer cela, mais cela fonctionne.

Alors

filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);

au lieu de

filterContext.RequestContext.HttpContext.Response.Redirect("/Login);

Donc, vous auriez ceci dans votre contrôleur:

 protected override void OnAuthorization(AuthorizationContext filterContext)
 {
      if(!User.IsInRole("Admin")
      {
          base.OnAuthorization(filterContext);
          filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
      }
 }
1
Lazy Coder

Vous obtenez peut-être une page vierge lorsque vous vous exécutez à partir de Visual Studio sous serveur de développement en utilisant l'authentification Windows ( sujet précédent ).

Si vous déployez vers IIS, vous pouvez configurer des pages d'erreur personnalisées pour des codes d'état spécifiques, dans le cas présent 401. Ajoutez httpErrors sous system.webServer:

<httpErrors>
  <remove statusCode="401" />
  <error statusCode="401" path="/yourapp/error/unauthorized" responseMode="Redirect" />
</httpErrors>

Créez ensuite la méthode ErrorController.Unauthorized et la vue personnalisée correspondante.

1
Mark Meyerovich

Vous devez créer votre propre attribut Authorize-filter.

Voici le mien pour étudier;)

Public Class RequiresRoleAttribute : Inherits ActionFilterAttribute
    Private _role As String

    Public Property Role() As String
        Get
            Return Me._role
        End Get
        Set(ByVal value As String)
            Me._role = value
        End Set
    End Property

    Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
        If Not String.IsNullOrEmpty(Me.Role) Then
            If Not filterContext.HttpContext.User.Identity.IsAuthenticated Then
                Dim redirectOnSuccess As String = filterContext.HttpContext.Request.Url.AbsolutePath
                Dim redirectUrl As String = String.Format("?ReturnUrl={0}", redirectOnSuccess)
                Dim loginUrl As String = FormsAuthentication.LoginUrl + redirectUrl

                filterContext.HttpContext.Response.Redirect(loginUrl, True)
            Else
                Dim hasAccess As Boolean = filterContext.HttpContext.User.IsInRole(Me.Role)
                If Not hasAccess Then
                    Throw New UnauthorizedAccessException("You don't have access to this page. Only " & Me.Role & " can view this page.")
                End If
            End If
        Else
            Throw New InvalidOperationException("No Role Specified")
        End If

    End Sub
End Class
1
Ropstah