如何创建特定于区域,控制器和操作的自定义AuthorizeAttribute?

时间:2011-02-03 17:36:48

标签: 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以访问路由信息并将其用作角色名称吗?正如列维所说,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;
}

3 个答案:

答案 0 :(得分:18)

不要这样做。

如果确实需要,可以使用控制器的 Type 或操作的 MethodInfo 来做出安全决策。但基于字符串的所有东西都在寻找麻烦。请记住,没有保证路由值到实际控制器的1:1映射。如果您正在使用路由元组(a,b,c)来验证对SomeController :: SomeAction的访问,但是有人发现(a,b',c)也会触发相同的操作,那么该人可以绕过您的安全机制。 / p>

修改以回复评论:

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

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

  

/ MyArea /富/ TheAction

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

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

  

/富/ TheAction

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

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

有关详情,请参阅我的回复:

答案 1 :(得分:5)

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

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,因为结果是将用户发送到登录页面。考虑到他们已经登录,这对用户来说非常困惑。

答案 2 :(得分:1)

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

祝你好运。