在 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 MVC3 中重定向到不影响 URL 的区域