asp.net-mvc 如何创建特定于区域、控制器和操作的自定义 AuthorizeAttribute?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4889541/
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
How do I create a custom AuthorizeAttribute that is specific to the area, controller and action?
提问by Rebecca
In other words, is this a really stupid idea?
换句话说,这是一个非常愚蠢的想法吗?
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeActionAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// get the area, controller and action
var area = filterContext.RouteData.Values["area"];
var controller = filterContext.RouteData.Values["controller"];
var action = filterContext.RouteData.Values["action"];
string verb = filterContext.HttpContext.Request.HttpMethod;
// these values combined are our roleName
string roleName = String.Format("{0}/{1}/{2}/{3}", area, controller, action, verb);
// set role name to area/controller/action name
this.Roles = roleName;
base.OnAuthorization(filterContext);
}
}
UPDATEI'm trying to avoid the following, in a scenario where we have extremely granular role permissions because the roles are setup on a per-client basis and attached to user groups:
更新我试图避免以下情况,在我们具有极其精细的角色权限的情况下,因为角色是基于每个客户端设置的并附加到用户组:
public partial class HomeController : Controller
{
[Authorize(Roles = "/supplierarea/homecontroller/indexaction/")]
public virtual ActionResult Index()
{
return View();
}
[Authorize(Roles = "/supplierarea/homecontroller/aboutaction/")]
public virtual ActionResult About()
{
return View();
}
}
Can anyone enlighten me to a secure way to write this AuthorizeRouteAttribute to access the route information and use this as the role name? As Levi says, the RouteData.Values isn't secure.
任何人都可以启发我编写此 AuthorizeRouteAttribute 以访问路由信息并将其用作角色名称的安全方法吗?正如 Levi 所说,RouteData.Values 并不安全。
Is the use of the executing httpContext.Request.Path any more secure or better practice?
使用正在执行的 httpContext.Request.Path 是否更安全或更好的做法?
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
var path = filterContext.HttpContext.Request.Path;
var verb = filterContext.HttpContext.Request.HttpMethod;
// these values combined are our roleName
string roleName = String.Format("{0}/{1}", path, verb);
if (!filterContext.HttpContext.User.IsInRole(roleName))
{
// role auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
// P.S. I want to tell the logged in user they don't
// have access, not ask them to login. They are already
// logged in!
return;
}
//
base.OnAuthorization(filterContext);
}
This maybe illustrates the issue a little further:
这可能更进一步说明了这个问题:
enum Version
{
PathBasedRole,
InsecureButWorks,
SecureButMissingAreaName
}
string GetRoleName(AuthorizationContext filterContext, Version version)
{
//
var path = filterContext.HttpContext.Request.Path;
var verb = filterContext.HttpContext.Request.HttpMethod;
// recommended way to access controller and action names
var controller =
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action =
filterContext.ActionDescriptor.ActionName;
var area = "oh dear...."; // mmmm, where's thearea name???
//
var insecureArea = filterContext.RouteData.Values["area"];
var insecureController = filterContext.RouteData.Values["controller"];
var insecureAction = filterContext.RouteData.Values["action"];
string pathRoleName =
String.Format("{0}/{1}", path, verb);
string insecureRoleName =
String.Format("{0}/{1}/{2}/{3}",
insecureArea,
insecureController,
insecureAction,
verb);
string secureRoleName =
String.Format("{0}/{1}/{2}/{3}",
area,
controller,
action,
verb);
string roleName = String.Empty;
switch (version)
{
case Version.InsecureButWorks:
roleName = insecureRoleName;
break;
case Version.PathBasedRole:
roleName = pathRoleName;
break;
case Version.SecureButMissingAreaName:
// let's hope they don't choose this, because
// I have no idea what the area name is
roleName = secureRoleName;
break;
default:
roleName = String.Empty;
break;
}
return roleName;
}
回答by Levi
Please do notdo this.
请不要这样做。
If you really need to, you can use the Typeof the controller or the MethodInfoof the action to make security decisions. But basing everything off of strings is asking for trouble. Remember, there's no guaranteed 1:1 mapping of Routing values to actual controller. If you're using the Routing tuple (a, b, c) to validate access to SomeController::SomeAction but somebody discovers that (a, b', c) also hits that same action, that person can bypass your security mechanisms.
如果确实需要,可以使用控制器的类型或操作的MethodInfo来做出安全决策。但一切都基于字符串是自找麻烦。请记住,没有保证路由值到实际控制器的 1:1 映射。如果您使用路由元组 (a, b, c) 来验证对 SomeController::SomeAction 的访问,但有人发现 (a, b', c) 也执行了相同的操作,则该人可以绕过您的安全机制。
Editto respond to comments:
编辑以回复评论:
You have access to the controller's Type and the action's MethodInfo via the filterContextparameter's ActionDescriptor property. This is the only sure-fire way to determine what action will reallyexecute when the MVC pipeline is processing, because it's possible that your lookup doesn't exactly match what's going on behind the scenes with MVC. Once you have the Type / MethodInfo / whatever, you can use whatever information you wish (such as their fully-qualified names) to make security decisions.
您可以通过filterContext参数的 ActionDescriptor 属性访问控制器的类型和操作的 MethodInfo 。这是确定当 MVC 管道正在处理时真正执行什么操作的唯一可靠方法,因为您的查找可能与 MVC 的幕后发生的事情不完全匹配。一旦你有了 Type/MethodInfo/whatever,你就可以使用任何你想要的信息(比如它们的完全限定名称)来做出安全决策。
As a practical example, consider an area MyArea with a controller FooController and an action TheAction. Normally the way that you would hit this FooController::TheAction is via this URL:
作为一个实际的例子,考虑一个带有控制器 FooController 和动作 TheAction 的区域 MyArea。通常你点击这个 FooController::TheAction 的方式是通过这个 URL:
/MyArea/Foo/TheAction
/MyArea/Foo/TheAction
And Routing gives the tuple (Area = "MyArea", Controller = "Foo", Action = "TheAction").
Routing 给出元组 (Area = "MyArea", Controller = "Foo", Action = "TheAction")。
However, you can also hit FooController::TheAction via this URL:
但是,您也可以通过此 URL 访问 FooController::TheAction:
/Foo/TheAction
/Foo/TheAction
And Routing will give the tuple (Area = "", Controller = "Foo", Action = "TheAction"). Remember, areas are associated with routes, not controllers. And since a controller can be hit by multiple routes (if the definitions match), then a controller can also be logically associated with multiple areas. This is why we tell developers never to use routes (or areas or the <location> tag, by extension) to make security decisions.
Routing 将给出元组 (Area = "", Controller = "Foo", Action = "TheAction")。请记住,区域与路线相关联,而不是与控制器相关联。并且由于控制器可以被多个路由命中(如果定义匹配),那么控制器也可以在逻辑上与多个区域相关联。这就是为什么我们告诉开发人员永远不要使用路由(或区域或 <location> 标签,通过扩展)来做出安全决策。
Additionally, there's a bug in your class in that it's mutable (it mutates its own Roles property in OnAuthorization). Action filter attributes must be immutable, since they may be cached by parts of the pipeline and reused. Depending on where this attribute is declared in your application, this opens a timing attack, which a malicious site visitor could then exploit to grant himself access to any action he wishes.
此外,您的类中有一个错误,因为它是可变的(它在 OnAuthorization 中改变了自己的 Roles 属性)。动作过滤器属性必须是不可变的,因为它们可能会被部分管道缓存并重用。根据在您的应用程序中声明该属性的位置,这会开启定时攻击,恶意站点访问者可以利用该攻击授予自己访问权限以执行他希望的任何操作。
For more info, see also my responses at:
有关更多信息,另请参阅我的回复:
回答by Rebecca
If you want to do this, taking Levi's recommendation into account, the answer is as follows:
如果你想这样做,考虑到李维斯的建议,答案如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
namespace MvcApplication1.Extension.Attribute
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeActionAttribute : AuthorizeAttribute
{
/// <summary>
/// Called when a process requests authorization.
/// </summary>
/// <param name="filterContext">The filter context, which encapsulates information for using <see cref="T:System.Web.Mvc.AuthorizeAttribute"/>.</param>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="filterContext"/> parameter is null.</exception>
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
// these values combined are our roleName
string roleName = GetRoleName(filterContext);
if (!filterContext.HttpContext.User.IsInRole(roleName))
{
filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
filterContext.Result = new RedirectResult("~/Error/Unauthorized");
return;
}
//
base.OnAuthorization(filterContext);
}
/// <summary>
/// Gets the name of the role. Theorectical construct that illustrates a problem with the
/// area name. RouteData is apparently insecure, but the area name is available there.
/// </summary>
/// <param name="filterContext">The filter context.</param>
/// <param name="version">The version.</param>
/// <returns></returns>
string GetRoleName(AuthorizationContext filterContext)
{
//
var verb = filterContext.HttpContext.Request.HttpMethod;
// recommended way to access controller and action names
var controllerFullName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
var actionName = filterContext.ActionDescriptor.ActionName;
return String.Format("{0}.{1}-{2}", controllerFullName, actionName, verb);
}
}
}
I did not want to provide a HttpUnauthorizedResult in the case of a user not being in role, because the result is to send the user to the login page. Considering that they are already logged in, this is extremely confusing to the user.
我不想在用户不在角色的情况下提供 HttpUnauthorizedResult,因为结果是将用户发送到登录页面。考虑到他们已经登录,这对用户来说非常混乱。
回答by QMaster
This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"];
instead of filterContext.RouteData.Values["area"];
这是一个简短的通知!一定要使用filterContext.RouteData.DataTokens["area"];
而不是filterContext.RouteData.Values["area"];
Good Luck.
祝你好运。