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

Posted

技术标签:

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

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

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

   ...

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

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

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

【问题讨论】:

【参考方案1】:

基于 AuthorizeAttribute 创建一个自定义授权属性并覆盖 OnAuthorization 以执行您希望它完成的检查。正常情况下,AuthorizeAttribute 会在授权检查失败时将过滤结果设置为 HttpUnauthorizedResult。您可以将其设置为 ViewResult (您的错误视图)。

编辑:我有几篇更详细的博文:

http://farm-fresh-code.blogspot.com/2011/03/revisiting-custom-authorization-in.html http://farm-fresh-code.blogspot.com/2009/11/customizing-authorization-in-aspnet-mvc.html

例子:

    [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”的附加角色(默认角色,未在属性中指定)。如果不是,它会返回一个错误,表明在获得授权时,用户不是该操作的有效角色。当用户被授权并处于有效角色(或超级用户)时,它会设置缓存策略以防止下游缓存 我在这里找到了更好的答案:***.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))
    
);

【讨论】:

以上是关于在 ASP.NET MVC 中重定向未经授权的控制器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 asp.net mvc 中重定向到正确的控制器操作

在 ASP.Net MVC3 中重定向到不影响 URL 的区域

防止在 asp.net core 2.2 中重定向到 /Account/Login

ASP.NET 核心,更改未经授权的默认重定向

如何在 ASP.NET MVC 中重定向到动态登录 URL

如何在控制器操作中重定向到 aspx 页面