在ASP.NET MVC中重定向未授权的控制器

时间:2009-06-10 17:34:25

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

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

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

如果不在“管理员”角色的用户导航到此控制器,则会显示空白屏幕。

我想要做的是将它们重定向到“您需要处于管理员角色才能访问此资源的视图”。

我想到的一种方法是在IsUserInRole()上检查每个操作方法,如果不在角色中,则返回此信息视图。但是,我必须在每一个打破DRY原则的行动中加入它,显然很难维护。

8 个答案:

答案 0 :(得分:69)

基于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 :(得分:25)

您可以使用自定义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();

答案 2 :(得分:9)

“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 ...

答案 3 :(得分: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)

答案 4 :(得分: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();

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

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

但出乎意料的是, 1)Response.Redirect(“/ Login”)不起作用:Action方法仍然被调用,并且 2)FormsAuthentication.RedirectToLoginPage();做同样的事情:Action方法仍然被调用!

对我来说这似乎是完全错误的,特别是对于后者:谁会认为FormsAuthentication.RedirectToLoginPage不会结束请求,或者做上面的filterContext.Result = new HttpUnauthorizedResult()的等效项?

答案 5 :(得分: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

答案 6 :(得分:1)

本来会留下这个作为评论,但我需要更多的代表,无论如何我只想提到Nicholas Peterson,或许可以将第二个参数传递给Redirect调用,告诉它结束响应会起作用。不是处理这个问题的最优雅方式,但实际上确实有效。

所以

2015.07.28 21:18:21 INFO  web[DbMigration] ==  AddRuleTags: migrating ====================================================
2015.07.28 21:18:21 INFO  web[DbMigration] -- create_table(:rule_tags, {})
2015.07.28 21:18:21 ERROR web[o.s.s.ui.JRubyFacade] Fail to upgrade database
An error has occurred, all later migrations canceled:

ActiveRecord::JDBCError: Table 'rule_tags' already exists: CREATE TABLE `rule_tags` (`id` int(11) auto_increment PRIMARY KEY, `tag` varchar(100)) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract_adapter.rb:227:in `log'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-jdbc-adapter-1.1.3/lib/arjdbc/jdbc/adapter.rb:183:in `execute'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/schema_statements.rb:109:in `create_table'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-jdbc-adapter-1.1.3/lib/arjdbc/mysql/adapter.rb:252:in `create_table'
    org/jruby/RubyKernel.java:2231:in `send'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:352:in `method_missing'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:328:in `say_with_time'
    jar:file:/E:/sonarqube/sonarqube-5.1.1/lib/server/jruby-complete-1.7.9.jar!/META-INF/jruby.home/lib/ruby/1.8/benchmark.rb:293:in `measure'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:328:in `say_with_time'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:348:in `method_missing'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/config/environment.rb:194:in `create_table'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/config/../lib/../db/migrate/489_add_rule_tags.rb:27:in `up'
    org/jruby/RubyKernel.java:2223:in `send'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:282:in `migrate'
    jar:file:/E:/sonarqube/sonarqube-5.1.1/lib/server/jruby-complete-1.7.9.jar!/META-INF/jruby.home/lib/ruby/1.8/benchmark.rb:293:in `measure'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:282:in `migrate'
    org/jruby/RubyKernel.java:2227:in `send'
    E:1:in `migrate'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:491:in `migrate'
    org/jruby/RubyProc.java:290:in `call'
    org/jruby/RubyProc.java:224:in `call'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:576:in `ddl_transaction'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:490:in `migrate'
    org/jruby/RubyArray.java:1613:in `each'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:477:in `migrate'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:401:in `up'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/migration.rb:383:in `migrate'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/config/../lib/database_version.rb:62:in `upgrade_and_start'
    E:/sonarqube/sonarqube-5.1.1/web/WEB-INF/app/models/database_migration_manager.rb:109:in `start_migration'
    org/jruby/RubyProc.java:290:in `call'
    org/jruby/RubyProc.java:228:in `call'

而不是

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

所以你在控制器中有这个:

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

答案 7 :(得分:1)

使用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方法和相应的自定义视图。