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

Reb*_*cca 8 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

任何人都可以通过安全的方式来启发我编写此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);
}
Run Code Online (Sandbox Code Playgroud)

这可能会进一步说明问题:

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;
}
Run Code Online (Sandbox Code Playgroud)

Lev*_*evi 18

要这样做.

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

编辑以回复评论:

您可以通过filterContext参数的ActionDescriptor属性访问控制器的Type和action的MethodInfo .这是确定在MVC管道处理时将实际执行什么操作的唯一确定方法,因为您的查找可能与MVC幕后发生的事件不完全匹配.获得Type/MethodInfo /之后,您可以使用您希望的任何信息(例如其完全限定名称)来做出安全决策.

作为一个实际示例,考虑一个带有控制器FooController的区域MyArea和一个动作TheAction.通常,您点击此FooController :: TheAction的方式是通过以下URL:

/ MyArea /富/ TheAction

并且路由给出了元组(Area ="MyArea",Controller ="Foo",Action ="TheAction").

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

/美孚/ TheAction

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

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

有关详细信息,请参阅我的回复:


Reb*_*cca 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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