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


76

我在ASP.NET MVC中有一个控制器,但仅限于管理员角色:

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

如果没有管理员角色的用户导航到该控制器,则会看到空白屏幕。

我想做的就是将他们重定向到View,上面写着“您必须具有管理员角色才能访问此资源。”

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

Answers:


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
我不认为有一个可以链接的链接可以将其分解为更容易理解的推理方法吗?
马斯洛(Maslow)2010年

1
不清楚吗?它首先使用AuthorizeCore来检查用户是否已被授权并处于允许的角色。否则,如果用户未通过身份验证,则会通过在过滤器的上下文中设置结果来返回未经授权的响应。如果已通过身份验证,则它将检查它是否具有“ SuperUser”的附加角色(默认角色,未在属性中指定)。如果不是,它将返回一个错误,指示在获得授权后,用户没有执行该操作的有效角色。在授权用户并使用有效角色(或超级用户)后,它将设置缓存策略以防止下游缓存
tvanfosson 2010年

我发现了一个更好的答案在这里: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 2013年

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

这个问题困扰了我好几天,因此在找到与上述特凡福森的答案肯定相符的答案时,我认为有必要强调答案的核心部分,并解决一些相关的问题。

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

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()的作用?


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 2013年

1

本来可以留个评论,但我需要更多代表,无论如何,我只想向尼古拉斯·彼得森(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);
      }
 }

1

当使用Windows身份验证在开发服务器下的Visual Studio中运行时,您可能会得到空白页(上一主题)。

如果部署到IIS,则可以为特定状态代码配置自定义错误页面,在这种情况下为401。在system.webServer下添加httpErrors:

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

然后创建ErrorController.Unauthorized方法和相应的自定义视图。


-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))
    }
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.