Nicht autorisierten Controller in ASP.NET MVC umleiten

76

Ich habe einen Controller in ASP.NET MVC, den ich auf die Administratorrolle beschränkt habe:

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

Wenn ein Benutzer, der nicht in der Administratorrolle ist, zu diesem Controller navigiert, wird er mit einem leeren Bildschirm begrüßt.

Ich möchte sie zu View umleiten, in dem steht, dass Sie in der Administratorrolle sein müssen, um auf diese Ressource zugreifen zu können.

Eine Möglichkeit, dies zu tun, besteht darin, jede Aktionsmethode in IsUserInRole () zu überprüfen und, falls nicht in der Rolle, diese Informationsansicht zurückzugeben. Allerdings müsste ich das in jede Aktion einfügen, die gegen das DRY-Prinzip verstößt und deren Wartung offensichtlich umständlich ist.

Kerl
quelle

Antworten:

71

Erstellen Sie ein benutzerdefiniertes Berechtigungsattribut basierend auf AuthorizeAttribute und überschreiben Sie OnAuthorization, um die gewünschte Überprüfung durchzuführen. Normalerweise setzt AuthorizeAttribute das Filterergebnis auf HttpUnauthorizedResult, wenn die Autorisierungsprüfung fehlschlägt. Sie können es stattdessen auf ein ViewResult (Ihrer Fehleransicht) setzen lassen.

EDIT : Ich habe ein paar Blog-Beiträge, die detaillierter gehen:

Beispiel:

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


    }
Tvanfosson
quelle
1
Ich nehme nicht an, dass es einen Link gibt, zu dem ich gehen kann, der dies in etwas leichter zu verfolgende Argumente unterteilt.
Maslow
1
Was ist nicht klar? Zunächst wird AuthorizeCore verwendet, um zu überprüfen, ob der Benutzer autorisiert ist und eine zulässige Rolle spielt. Wenn nicht, gibt der Benutzer, wenn er nicht authentifiziert ist, eine nicht autorisierte Antwort zurück, indem das Ergebnis im Kontext des Filters festgelegt wird. Wenn es authentifiziert ist, prüft es, ob es sich in der zusätzlichen Rolle "SuperUser" befindet (eine Standardrolle, die im Attribut nicht angegeben ist). Wenn nicht, wird ein Fehler zurückgegeben, der angibt, dass der Benutzer während der Autorisierung keine gültige Rolle für die Aktion spielt. Wenn der Benutzer autorisiert ist und eine gültige Rolle (oder einen SuperUser) hat, legt er die Cache-Richtlinie fest, um das Downstream-Caching zu verhindern
tvanfosson
Ich habe hier eine bessere Antwort gefunden: stackoverflow.com/questions/1498727/…
bluee
Es bleibt zu erwähnen, dass Sie mit dieser Lösung die Klasse oder Methode, die Sie steuern möchten, mit diesem Attribut "dekorieren" müssen: [MasterEventAuthorizationAttribute]
netfed
@netfed Sie können es auch als globales Attribut hinzufügen, obwohl Sie das Handling für AllowAnonymousAttribute hinzufügen müssen (das zum Zeitpunkt des Schreibens nicht vorhanden war).
Tvanfosson
27

Sie können mit dem Overridable HandleUnauthorizedRequestin Ihrem Benutzer arbeitenAuthorizeAttribute

So was:

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

Sie können auch so etwas tun:

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

Jetzt können Sie es in Ihrer HandleUnauthorizedRequestMethode folgendermaßen verwenden:

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

Der Code von "tvanfosson" gab mir "Fehler beim Ausführen der untergeordneten Anforderung". Ich habe die OnAuthorization folgendermaßen geändert:

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

Dies funktioniert gut und ich zeige die TempData auf der Fehlerseite. Vielen Dank an "tvanfosson" für das Code-Snippet. Ich verwende die Windows-Authentifizierung und _isAuthorized ist nichts anderes als HttpContext.User.Identity.IsAuthenticated ...

Sajoshi
quelle
Gibt dies ein 401 für die URL zurück, zu der der Benutzer keine Berechtigung hat?
DevDave
5

Ich hatte das gleiche Problem. Anstatt den MVC-Code herauszufinden, entschied ich mich für einen billigen Hack, der zu funktionieren scheint. In meiner Global.asax-Klasse:

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)
MichaelGG
quelle
2

Dieses Problem hat mich seit einigen Tagen verfolgt. Als ich die Antwort fand, die mit der obigen Antwort von tvanfosson positiv zusammenarbeitet, hielt ich es für sinnvoll, den Kernteil der Antwort hervorzuheben und einige verwandte Probleme anzusprechen.

Die Kernantwort lautet: süß und einfach:

filterContext.Result = new HttpUnauthorizedResult();

In meinem Fall erbe ich von einem Basis-Controller. In jedem Controller, der von diesem erbt, überschreibe ich OnAuthorize:

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

Das Problem war, dass ich in 'YourAuth' zwei Dinge ausprobiert habe, von denen ich dachte, dass sie nicht nur funktionieren, sondern auch die Anfrage sofort beenden würden. Nun, so funktioniert es nicht. Also zuerst die zwei Dinge, die NICHT unerwartet funktionieren:

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

Diese funktionieren nicht nur nicht, sie beenden auch die Anfrage nicht. Was bedeutet folgendes:

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

Nun, auch mit der richtigen Antwort oben geht der Logikfluss weiter! Sie werden weiterhin DoMoreStuff ... in OnAuthorize drücken. Denken Sie also daran (DoMore ... sollte daher in einem anderen sein).

Aber mit der richtigen Antwort, während der OnAuthorize-Logikfluss noch bis zum Ende andauert, erhalten Sie danach wirklich das, was Sie erwarten: eine Weiterleitung zu Ihrer Anmeldeseite (wenn Sie eine in Forms auth in Ihrer Webkonfiguration festgelegt haben).

Aber unerwartet funktioniert 1) Response.Redirect ("/ Login") nicht: Die Action-Methode wird weiterhin aufgerufen, und 2) FormsAuthentication.RedirectToLoginPage (); macht das gleiche: Die Action-Methode wird immer noch aufgerufen!

Was mir völlig falsch erscheint, insbesondere bei letzterem: Wer hätte gedacht, dass FormsAuthentication.RedirectToLoginPage die Anforderung nicht beendet, oder das Äquivalent darüber, was filterContext.Result = new HttpUnauthorizedResult () tut?

Nicholas Petersen
quelle
1

Sie sollten Ihr eigenes Authorize-Filter-Attribut erstellen.

Hier ist meins zum Lernen;)

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
Ropstah
quelle
Dies scheint umzuleiten, aber es scheint auch, dass die gesamte Aktion zuerst ausgeführt wird.
Mike Cole
Anstatt eine Weiterleitung durchzuführen, sollten Sie dies tunfilterContext.Result = new RedirectResult(loginUrl)
Mike Cole
1

Hätte dies als Kommentar hinterlassen, aber ich brauche mehr Repräsentanten, trotzdem wollte ich Nicholas Peterson nur erwähnen, dass es vielleicht funktioniert hätte, das zweite Argument an den Redirect-Aufruf zu übergeben, um ihm zu sagen, dass die Antwort beendet werden soll. Nicht die anmutigste Art, damit umzugehen, aber es funktioniert tatsächlich.

Damit

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

anstatt

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

Das hätten Sie also in Ihrem Controller:

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

Möglicherweise wird eine leere Seite angezeigt, wenn Sie Visual Studio unter einem Entwicklungsserver mit Windows-Authentifizierung ausführen ( vorheriges Thema) ).

Wenn Sie auf IIS bereitstellen, können Sie benutzerdefinierte Fehlerseiten für bestimmte Statuscodes konfigurieren, in diesem Fall 401. Fügen Sie unter system.webServer httpErrors hinzu:

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

Erstellen Sie dann die ErrorController.Unauthorized-Methode und die entsprechende benutzerdefinierte Ansicht.

Mark Meyerovich
quelle
-1

Fügen Sie in Ihrer Datei Startup.Auth.cs die folgende Zeile hinzu:

LoginPath = new PathString("/Account/Login"),

Beispiel:

// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
        validateInterval: TimeSpan.FromMinutes(30),
        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
});
Ehren Van Auken
quelle