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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/977071/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 04:41:57  来源:igfitidea点击:

Redirecting unauthorized controller in ASP.NET MVC

c#asp.net-mvcredirectauthorization

提问by Guy

I have a controller in ASP.NET MVC that I've restricted to the admin role:

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

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

If a user who is not in the Admin role navigates to this controller they are greeted with a blank screen.

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

What I would like to do is redirect them to View that says "you need to be in the Admin role to be able to access this resource."

我想要做的是将它们重定向到 View,上面写着“您需要具有 Admin 角色才能访问此资源”。

One way of doing this that I've thought of is to have a check in each action method on IsUserInRole() and if not in role then return this informational view. However, I'd have to put that in each Action which breaks the DRY principal and is obviously cumbersome to maintain.

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

采纳答案by tvanfosson

Create a custom authorization attribute based on AuthorizeAttribute and override OnAuthorization to perform the check how you want it done. Normally, AuthorizeAttribute will set the filter result to HttpUnauthorizedResult if the authorization check fails. You could have it set it to a ViewResult (of your Error view) instead.

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

EDIT: I have a couple of blog posts that go into more detail:

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

Example:

例子:

    [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 */);
        }


    }

回答by Ropstah

You should build your own Authorize-filter attribute.

您应该构建自己的 Authorize-filter 属性。

Here's mine to study ;)

这是我要学习的 ;)

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

回答by MichaelGG

I had the same issue. Rather than figure out the MVC code, I opted for a cheap hack that seems to work. In my Global.asax class:

我遇到过同样的问题。我没有弄清楚 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)

回答by sajoshi

The code by "tvanfosson" was giving me "Error executing Child Request".. I have changed the OnAuthorization like this:

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

This works well and I show the TempData on error page. Thanks to "tvanfosson" for the code snippet. I am using windows authentication and _isAuthorized is nothing but HttpContext.User.Identity.IsAuthenticated...

这很有效,我在错误页面上显示了 TempData。感谢“tvanfosson”的代码片段。我正在使用 Windows 身份验证,而 _isAuthorized 只不过是 HttpContext.User.Identity.IsAuthenticated ...

回答by Leniel Maccaferri

You can work with the overridable HandleUnauthorizedRequestinside your custom AuthorizeAttribute

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

Like this:

像这样:

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

You can also do something like this:

你也可以做这样的事情:

private class RedirectController : Controller
{
    public ActionResult RedirectToSomewhere()
    {
        return RedirectToAction("Action", "Controller");
    }
}

Now you can use it in your HandleUnauthorizedRequestmethod this way:

现在您可以通过HandleUnauthorizedRequest以下方式在您的方法中使用它:

filterContext.Result = (new RedirectController()).RedirectToSomewhere();

回答by Nicholas Petersen

This problem has hounded me for some days now, so on finding the answer that affirmatively works with tvanfosson's answer above, I thought it would be worthwhile to emphasize the core part of the answer, and address some related catch ya's.

这个问题已经困扰了我好几天了,所以在找到与上面 tvanfosson 的答案肯定有效的答案后,我认为有必要强调答案的核心部分,并解决一些相关的问题。

The core answer is this, sweet and simple:

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

filterContext.Result = new HttpUnauthorizedResult();

In my case I inherit from a base controller, so in each controller that inherits from it I override OnAuthorize:

在我的例子中,我从一个基本控制器继承,所以在从它继承的每个控制器中,我覆盖 OnAuthorize:

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    base.OnAuthorization(filterContext);
    YourAuth(filterContext); // do your own authorization logic here
}

The problem was that in 'YourAuth', I tried two things that I thought would not only work, but would also immediately terminate the request. Well, that is not how it works. So first, the two things that DO NOT work, unexpectedly:

问题在于,在“YourAuth”中,我尝试了两件事,我认为这不仅会奏效,而且还会立即终止请求。嗯,这不是它的工作原理。所以首先,出乎意料的是,这两件事不起作用:

filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work!
FormsAuthentication.RedirectToLoginPage(); // doesn't work!

Not only do those not work, they don't end the request either. Which means the following:

这些不仅不起作用,而且它们也不会结束请求。这意味着以下内容:

if (!success) {
    filterContext.Result = new HttpUnauthorizedResult();
}
DoMoreStuffNowThatYouThinkYourAuthorized();

Well, even with the correct answer above, the flow of logic still continues! You will still hit DoMoreStuff... within OnAuthorize. So keep that in mind (DoMore... should be in an else therefore).

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

But with the correct answer, while OnAuthorize flow of logic continues till the end still, after that you really do get what you expect: a redirect to your login page (if you have one set in Forms auth in your webconfig).

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

But unexpectedly, 1) Response.Redirect("/Login") does not work: the Action method still gets called, and 2) FormsAuthentication.RedirectToLoginPage(); does the same thing: the Action method still gets called!

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

Which seems totally wrong to me, particularly with the latter: who would have thought that FormsAuthentication.RedirectToLoginPage does not end the request, or do the equivalant above of what filterContext.Result = new HttpUnauthorizedResult() does?

这对我来说似乎完全错误,尤其是后者:谁会想到 FormsAuthentication.RedirectToLoginPage 不会结束请求,或者做上面的 filterContext.Result = new HttpUnauthorizedResult() 的等效操作?

回答by Lazy Coder

Would have left this as a comment but I need more rep, anyways I just wanted to mention to Nicholas Peterson that perhaps passing the second argument to the Redirect call to tell it to end the response would have worked. Not the most graceful way to handle this but it does in fact work.

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

So

所以

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

instead of

代替

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

So you'd have this in your controller:

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

 protected override void OnAuthorization(AuthorizationContext filterContext)
 {
      if(!User.IsInRole("Admin")
      {
          base.OnAuthorization(filterContext);
          filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
      }
 }

回答by Mark Meyerovich

Perhaps you get a blank page when you run from Visual Studio under development server using Windows authentication (previous topic).

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

If you deploy to IIS you can configure custom error pages for specific status codes, in this case 401. Add httpErrors under system.webServer:

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

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

Then create ErrorController.Unauthorized method and corresponding custom view.

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

回答by Ehren Van Auken

In your Startup.Auth.cs file add this line:

在您的 Startup.Auth.cs 文件中添加以下行:

LoginPath = new PathString("/Account/Login"),

Example:

例子:

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