Подтвердить что ты не робот

Перенаправление неавторизованного контроллера в ASP.NET MVC

У меня есть контроллер в ASP.NET MVC, который я ограничил роль администратора:

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

Если пользователь, который не входит в роль администратора, переходит к этому контроллеру, их приветствует пустой экран.

То, что я хотел бы сделать, это перенаправить их на View, в котором говорится: "вам нужно быть в роли администратора, чтобы иметь доступ к этому ресурсу".

Один из способов сделать это, о котором я думал, - это проверить каждый метод действия на IsUserInRole(), а если нет в роли, верните это информационное представление. Тем не менее, я должен был бы добавить это в каждое действие, которое нарушает принцип DRY и, очевидно, громоздко поддерживать.

4b9b3361

Ответ 1

Создайте настраиваемый атрибут авторизации на основе 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 */);
        }


    }

Ответ 2

Вы можете работать с переопределяемым 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();

Ответ 3

Код "tvanfosson" давал мне "Ошибка выполнения Child Request". Я изменил 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...

Ответ 4

У меня была такая же проблема. Вместо того, чтобы определять код 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)

Ответ 5

Эта проблема преследовала меня в течение нескольких дней, поэтому, найдя ответ, который утвердительно работает с ответом tvanfosson выше, я подумал, что было бы целесообразно подчеркнуть основную часть ответа и обратиться к некоторым связанным ловушкам ya's.

Основной ответ таков: сладкий и простой:

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()?

Ответ 6

Вы должны создать свой собственный атрибут 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

Ответ 7

Оставил бы это как комментарий, но мне нужно больше rep, так или иначе, я просто хотел упомянуть Николаса Петерсона, что, возможно, передав второй аргумент вызову 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);
      }
 }

Ответ 8

Возможно, вы получаете пустую страницу при запуске из Visual Studio под сервером разработки с использованием проверки подлинности Windows (предыдущий раздел).

При развертывании в IIS вы можете настроить собственные страницы ошибок для определенных кодов состояния, в этом случае 401. Добавить httpErrors в файле system.webServer:

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

Затем создайте метод ErrorController.Unauthorized и соответствующий пользовательский вид.

Ответ 9

В файле 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))
    }
});