【问题标题】:How do I create a custom AuthorizeAttribute that is specific to the area, controller and action?如何创建特定于区域、控制器和操作的自定义 AuthorizeAttribute?
【发布时间】:2013-02-08 00:59:00
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

    不要这样做。

    如果确实需要,可以使用控制器的Type 或操作的MethodInfo 来做出安全决策。但是,一切都建立在字符串的基础上是自找麻烦。请记住,路由值与实际控制器的映射不能保证 1:1。如果您使用路由元组 (a, b, c) 来验证对 SomeController::SomeAction 的访问,但有人发现 (a, b', c) 也执行了相同的操作,则该人可以绕过您的安全机制。

    编辑以回应 cmets:

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

    作为一个实际的例子,考虑一个带有控制器 FooController 和一个动作 TheAction 的区域 MyArea。通常你会点击这个 FooController::TheAction 的方式是通过这个 URL:

    /MyArea/Foo/TheAction

    Routing 给出了元组 (Area = "MyArea", Controller = "Foo", Action = "TheAction")。

    但是,您也可以通过此 URL 访问 FooController::TheAction:

    /Foo/TheAction

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

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

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

    【讨论】:

    • 请您添加到您的答案中以显示您的建议如何在代码中起作用?我们正在使用区域,因此它需要反映这一点以及控制器和操作。出于兴趣,您真的可以部分匹配控制器或操作(即如建议的那样:/myarea/mycontroller/myaction';DROP TABLE 成员;--/)吗? MVC 肯定不会首先匹配控制器或动作?
    • 更新回复以解决您的问题。
    • 您好 Levi,我知道我可以使用 (string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; string action = filterContext.ActionDescriptor.ActionName;) 但我无法访问同样的方法。但是,没有可用的 AreaName。我在哪里可以找到它?一个简单的代码示例就可以结束这个问题。
    • 另外,关于“你的类中的错误是可变的”。这是否意味着我应该自己检查上下文 IsInRole() 中的当前用户,如果不正确则重定向?
    • 请阅读我链接到的项目。您不应使用该区域来做出安全决定。没有例外。 唯一 一个区域有用的是根据传入的 URL 对您的站点进行分区。由于 URL 不应该用于在 MVC 应用程序中做出安全决定,因此该区域也不应该。您需要重新制定应用程序安全决策的方式。
    【解决方案2】:

    如果你想这样做,考虑到李维斯的建议,答案如下:

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

    我不想在用户不在角色的情况下提供 HttpUnauthorizedResult,因为结果是将用户发送到登录页面。考虑到他们已经登录,这对用户来说非常混乱。

    【讨论】:

    • 不使用 OnAuthorization 来使用 AuthorizeCore - stackoverflow.com/questions/5989100/… 怎么样?
    • @gw0,你认为如何在AuthorizeCore 中获得filterContext
    • @Junto,在您自定义的OnAuthorization() 末尾调用base.OnAuthorization(filterContext); 有什么问题?
    • @Junto 只是想知道为什么要调用 base.OnAuthorization(filterContext);什么时候覆盖它?
    【解决方案3】:

    这是一个简短的通知!请务必使用filterContext.RouteData.DataTokens["area"]; 而不是filterContext.RouteData.Values["area"];

    祝你好运。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多