asp.net-mvc - 如何创建特定于区域、 Controller 和操作的自定义 AuthorizeAttribute?

标签 asp.net-mvc attributes asp.net-mvc-3

换句话说,这是一个非常愚蠢的想法吗?

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

更新 在我们拥有极其精细的角色权限的情况下,我试图避免以下情况,因为角色是基于每个客户端设置并附加到用户组的:

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

任何人都可以告诉我一种安全的方法来编写此 AuthorizeRouteAttribute 来访问路由信息并将其用作角色名称吗?正如 Levi 所说,RouteData.Values 并不安全。

使用执行 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);
}

这可能进一步说明了这个问题:

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

最佳答案

不要这样做。

如果确实需要,您可以使用 Controller 的类型或操作的MethodInfo来做出安全决策。但如果一切都脱离实际,那就是自找麻烦。请记住,不保证路由值到实际 Controller 的 1:1 映射。如果您使用路由元组 (a, b, c) 来验证对 SomeController::SomeAction 的访问,但有人发现 (a, b', c) 也执行了相同的操作,则该人可以绕过您的安全机制。

编辑以回复评论:

您可以通过 filterContext 参数的 ActionDescriptor 属性访问 Controller 的 Type 和操作的 MethodInfo。这是确定 MVC 管道处理时真正执行什么操作的唯一可靠方法,因为您的查找可能与 MVC 幕后发生的事情不完全匹配。一旦您获得了类型/方法信息/其他信息,您就可以使用您想要的任何信息(例如它们的完全限定名称)来做出安全决策。

作为一个实际示例,请考虑具有 Controller FooController 和操作 TheAction 的区域 MyArea。通常,您点击此 FooController::TheAction 的方式是通过以下 URL:

/MyArea/Foo/TheAction

路由给出元组(Area =“MyArea”,Controller =“Foo”,Action =“TheAction”)。

但是,您也可以通过以下 URL 点击 FooController::TheAction:

/Foo/TheAction

路由将给出元组(Area =“”,Controller =“Foo”,Action =“TheAction”)。请记住,区域与路线相关,而不是与 Controller 相关。并且由于一个 Controller 可以被多个路由命中(如果定义匹配),那么一个 Controller 也可以在逻辑上与多个区域关联。这就是为什么我们告诉开发人员永远不要使用路线(或区域或 标记,通过扩展)来做出安全决策。

此外,您的类中存在一个错误,因为它是可变的(它在 OnAuthorization 中改变了自己的 Roles 属性)。操作过滤器属性必须是不可变的,因为它们可能被管道的某些部分缓存并重用。根据应用程序中声明此属性的位置,这会引发定时攻击,恶意网站访问者可以利用该攻击来授予自己访问他希望执行的任何操作的权限。

有关更多信息,另请参阅我的回复:

关于asp.net-mvc - 如何创建特定于区域、 Controller 和操作的自定义 AuthorizeAttribute?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4889541/

相关文章:

c# - 传递可为空的 Guid

ruby-on-rails - Rails - 使用 JSON 从 Controller 返回带有对象的附加属性

r - 默认名称连接

php - 更新产品属性的更有效方法,Magento

jquery - 如何在外部JS函数中提及操作方法的路径?

asp.net - 如何让 Autofac 在 Orchard CMS 中执行属性注入(inject)

asp.net-mvc - 准备我的 ASP.NET/MVC 站点以使用 SSL?

asp.net-mvc - 为什么即使授予 IIS_IUSRS 对该目录的写权限,我仍然收到 "Access to the path ' C :\. ..\.. .' is denied"?

javascript - 如果数据来自单独的 ajax 调用,如何为 jqGrid 添加服务器端分页?[Trirand JQgrid 4.6]

.net - 从 Orchard 内的主题渲染图像