Перенаправлення несанкціонованого контролера в ASP.NET MVC


76

У мене є контролер в ASP.NET MVC, який я обмежив роллю адміністратора:

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

Якщо користувач, який не виконує роль адміністратора, переходить до цього контролера, він вітається з порожнім екраном.

Що я хотів би зробити, це перенаправити їх на Перегляд, який говорить: "Вам потрібно бути в ролі адміністратора, щоб мати доступ до цього ресурсу".

Одним із способів зробити це, про що я думав, є перевірка кожного методу дії на IsUserInRole (), а якщо не в ролі, поверніть це інформаційне подання. Однак я повинен був би це вказувати в кожній дії, яка порушує принцип СУХОСТІ і, очевидно, громіздка в обслуговуванні.

Відповіді:


71

Створіть власний атрибут авторизації на основі AuthorizeAttribute і перевизначте OnAuthorization, щоб виконати перевірку, як ви хочете це зробити. Зазвичай AuthorizeAttribute встановлює результат фільтра на HttpUnauthorizedResult, якщо перевірка авторизації не вдається. Ви можете замість нього встановити для ViewResult (для вашого перегляду помилок).

РЕДАГУВАТИ : У мене є кілька дописів у блозі, які детальніше описують:

Приклад:

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


    }

1
Я не думаю, що існує посилання, до якого я можу перейти, що розбиває це на трохи легші для слідування міркування?
Маслоу

1
Що незрозуміло? Спочатку він використовує AuthorizeCore, щоб перевірити, чи користувач авторизований і чи має дозволену роль. Якщо ні, якщо користувач не аутентифікований, він повертає несанкціоновану відповідь, встановлюючи результат у контексті фільтра. Якщо він автентифікований, він перевіряє, чи є він у додатковій ролі "SuperUser" (роль за замовчуванням, не вказана в атрибуті). Якщо ні, він повертає помилку, яка вказує, що користувач, не маючи дозволу, не виконує дійсну роль для дії. Коли користувач авторизований і має дійсну роль (або SuperUser), він встановлює політику кешу для запобігання кешуванню в
потоці

Я знайшов найкращий відповідь тут: stackoverflow.com/questions/1498727 / ...
bluee

Залишилося згадати, що за допомогою цього рішення вам доведеться "прикрасити" клас або метод, яким ви хочете керувати, за допомогою цього атрибута: [MasterEventAuthorizationAttribute]
netfed

@netfed ви також можете додати його як глобальний атрибут, хоча вам потрібно буде додати в обробці для AllowAnonymousAttribute (який не існував, коли я писав це).
tvanfosson

27

Ви можете працювати з перезаписуваними HandleUnauthorizedRequestвсередині вашого замовленняAuthorizeAttribute

Подобається це:

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

Ви також можете зробити щось подібне:

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

Тепер ви можете використовувати його у своєму HandleUnauthorizedRequestметоді таким чином:

filterContext.Result = (new RedirectController()).RedirectToSomewhere();

10

Код "tvanfosson" давав мені "Помилка при виконанні дочірнього запиту" .. Я змінив OnAuthorization таким чином:

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

Це працює добре, і я показую TempData на сторінці помилок. Дякуємо "tvanfosson" за фрагмент коду. Я використовую автентифікацію Windows, а _isAuthorized - це не що інше, як HttpContext.User.Identity.IsAuthenticated ...


Чи повертає це URL-адресу 401, на яку користувач не має дозволу?
DevDave

5

У мене була та сама проблема. Замість того, щоб розібратися з кодом MVC, я вибрав дешевий хак, який, здається, працює. У моєму класі 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)

2

Ця проблема переслідує мене вже кілька днів, тому, знаходячи відповідь, яка ствердно працює з відповіддю tvanfosson вище, я вважав, що варто підкреслити основну частину відповіді та розглянути деякі пов’язані з цим проблеми.

Основна відповідь така, мила і проста:

filterContext.Result = new HttpUnauthorizedResult();

У моєму випадку я успадковую від базового контролера, тому в кожному контролері, який успадковує від нього, я замінюю OnAuthorize:

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

Проблема полягала в тому, що у "YourAuth" я спробував дві речі, які, на мою думку, не лише спрацюють, але й негайно припинять запит. Ну, це не так це працює. Отже, спочатку дві речі, які НЕ працюють, несподівано:

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

Вони не тільки не працюють, вони також не закінчують запит. Це означає наступне:

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

Ну, навіть при правильній відповіді вище, потік логіки все одно триває! Ви все одно натиснете DoMoreStuff ... в OnAuthorize. Тож майте це на увазі (отже DoMore ... повинен бути в іншому).

Але з правильною відповіддю, поки потік логіки OnAuthorize продовжується до кінця, після цього ви дійсно отримуєте те, що очікуєте: переспрямування на вашу сторінку входу (якщо у вас є одна установка в Forms auth у вашому webconfig).

Але несподівано, 1) Response.Redirect ("/ Login") не працює: метод Action все ще викликається, і 2) FormsAuthentication.RedirectToLoginPage (); робить те саме: метод Action все ще викликається!

Що мені здається абсолютно неправильним, особливо з останнім: хто б міг подумати, що FormsAuthentication.RedirectToLoginPage не закінчує запит або робить еквівалент вище того, що робить filterContext.Result = new HttpUnauthorizedResult ()?


1

Вам слід створити власний атрибут Authorize-filter.

Ось моя для вивчення;)

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

Здається, це переспрямовує, але, здається, спочатку виконується повністю за оригінальним методом дії.
Mike Cole

Замість того, щоб робити переспрямування, вам слід це зробитиfilterContext.Result = new RedirectResult(loginUrl)
Mike Cole

1

Залишив би це як коментар, але мені потрібні додаткові репутації, у будь-якому випадку я просто хотів би згадати Ніколасу Пітерсону, що, можливо, передача другого аргументу переклику Redirect, щоб сказати йому, щоб закінчити відповідь, спрацювала. Не найвитонченіший спосіб вирішити це, але насправді він працює.

Так

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

замість

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

Отже, ви мали б це у своєму контролері:

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

1

Можливо, ви отримуєте порожню сторінку під час запуску з Visual Studio на сервері розробки за допомогою автентифікації Windows ( попередня тема ).

Якщо ви розгортаєтеся в IIS, ви можете налаштувати власні сторінки помилок для певних кодів стану, в даному випадку 401. Додайте httpErrors під system.webServer:

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

Потім створіть ErrorController.Uauthorized метод та відповідний власний вигляд.


-1

У файл Startup.Auth.cs додайте цей рядок:

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

Приклад:

// 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))
    }
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.