【问题标题】:Redirecting unauthorized controller in ASP.NET MVC在 ASP.NET MVC 中重定向未经授权的控制器
【发布时间】:2010-11-01 21:37:50
【问题描述】:

我在 ASP.NET MVC 中有一个控制器,我已将其限制为管理员角色:

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

如果不是管理员角色的用户导航到此控制器,他们会看到一个空白屏幕。

我想做的是将他们重定向到显示“您需要具有管理员角色才能访问此资源”的视图。

我想到的一种方法是检查 IsUserInRole() 上的每个操作方法,如果不在角色中,则返回此信息视图。但是,我必须将它放在每个 Action 中,这会破坏 DRY 原则并且维护起来显然很麻烦。

【问题讨论】:

    标签: c# asp.net-mvc redirect authorization


    【解决方案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 */);
            }
    
    
        }
    

    【讨论】:

    • 我不认为有一个链接可以将其分解为更容易理解的推理?
    • 什么不清楚?它首先使用 AuthorizeCore 检查用户是否已获得授权以及是否处于允许的角色。如果没有,如果用户未通过身份验证,它会通过在过滤器的上下文中设置结果来返回未经授权的响应。如果已通过身份验证,则检查它是否处于“SuperUser”的附加角色(默认角色,未在属性中指定)。如果不是,它会返回一个错误,表明在获得授权时,用户不是该操作的有效角色。当用户被授权并处于有效角色(或超级用户)时,它会设置缓存策略以防止下游缓存
    • 我在这里找到了更好的答案:stackoverflow.com/questions/1498727/…
    • 值得一提的是,使用此解决方案,您必须使用此属性“装饰”要控制的类或方法:[MasterEventAuthorizationAttribute]
    • @netfed 您也可以将其添加为全局属性,但您需要添加对 AllowAnonymousAttribute 的处理(我写这篇文章时它不存在)。
    【解决方案2】:

    您可以在自定义 AuthorizeAttribute 中使用可覆盖的 HandleUnauthorizedRequest

    像这样:

    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”的代码给了我“执行子请求时出错”。我已经像这样更改了 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”的代码 sn-p。我正在使用 Windows 身份验证,_isAuthorized 只不过是 HttpContext.User.Identity.IsAuthenticated...

      【讨论】:

      • 这是否会在用户无权访问的 url 上返回 401?
      【解决方案4】:

      我遇到了同样的问题。我没有弄清楚 MVC 代码,而是选择了一种似乎可行的廉价 hack。在我的 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 的答案肯定有效的答案后,我认为值得强调答案的核心部分,并解决一些相关的问题。

        核心答案是这样的,甜蜜而简单:

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

        好吧,即使上面的答案正确,逻辑流仍然继续!您仍然会在 OnAuthorize 中点击 DoMoreStuff...。所以请记住这一点(DoMore... 因此应该在 else 中)。

        但是有了正确的答案,虽然 OnAuthorize 逻辑流一直持续到最后,但之后您确实得到了您所期望的:重定向到您的登录页面(如果您在 webconfig 中的 Forms auth 中设置了一个)。

        但没想到, 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
          

          【讨论】:

          • 这似乎是重定向,但它也似乎首先在原始操作方法上运行整个。
          • 不要做重定向,你应该做filterContext.Result = new RedirectResult(loginUrl)
          【解决方案7】:

          本来可以将此作为评论,但我需要更多代表,无论如何我只是想向 Nicholas Peterson 提一下,也许将第二个参数传递给 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】:

            当您在开发服务器下使用 Windows 身份验证 (previous topic) 从 Visual Studio 运行时,您可能会得到一个空白页。

            如果您部署到 IIS,您可以为特定状态代码配置自定义错误页面,在本例中为 401。在 system.webServer 下添加 httpErrors:

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

              【讨论】:

                猜你喜欢
                • 2012-01-23
                • 2020-07-29
                • 2020-04-25
                • 2011-12-01
                • 1970-01-01
                • 2014-05-28
                • 1970-01-01
                • 1970-01-01
                • 2020-10-30
                相关资源
                最近更新 更多