2011-02-03 123 views
7

换句话说,这是一个非常愚蠢的想法吗?如何创建特定于区域,控制器和操作的自定义AuthorizeAttribute?

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

UPDATE 我试图避免以下,在我们有极其精细的角色权限,因为角色是在每个客户端的基础设置和连接到用户群的情景:

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

回答

18

做到这一点。

如果你真的需要,你可以使用控制器的类型或行动的MethodInfo做出安全决策。但把所有东西都放在字符串上就是在寻求麻烦。请记住,路由值与实际控制器之间不存在1:1映射。如果您使用路由元组(a,b,c)来验证对SomeController :: SomeAction的访问权限,但有人发现(a,b',c)也会触发相同的操作,则此人可以绕过您的安全机制。

编辑回应评论:

你必须通过filterContext参数的ActionDescriptor属性访问控制器的类型和行为的MethodInfo的。这是确定在MVC管道处理时执行什么动作确实执行什么动作的唯一可靠方法,因为您的查找可能与MVC背后的情况不完全匹配。一旦你有了类型/方法信息/任何东西,你就可以使用你希望的任何信息(比如完全限定的名字)来做出安全决定。

作为一个实际的例子,考虑一个带控制器FooController的区域MyArea和一个动作TheAction。通常情况下,你会打这个FooController的方式:: TheAction是通过这个网址:

/MyArea /美孚/ TheAction

和路由给人的元组(面积= “MyArea”,控制器=“ Foo“,Action =”TheAction“)。

/美孚/ TheAction

和路由将给元组(面积= “”,控制器=“富:

但是,您也可以通过这个网址打FooController的:: TheAction “,Action =”TheAction“)。请记住,区域与路线相关,而不是控制器。而且由于控制器可以被多条路径命中(如果定义匹配),那么控制器也可以在逻辑上与多个区域相关联。这就是为什么我们告诉开发者永远不要使用路由(或区域或<位置>标签)来做出安全决定。

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

欲了解更多信息,也请参阅我的回应:

+0

请你可以添加到你的答案,以显示你的建议如何在代码中工作?我们正在使用区域,所以它需要反映这一点以及控制器和操作。出于兴趣,你真的可以匹配一个控制器或行动(即建议:/ myarea/mycontroller/myaction'; DROP TABLE members; - /)?毫无疑问,MVC不会与首先与控制器或操作相匹配? – Junto 2011-02-03 21:24:11

+0

解决您的问题的更新回应。 – Levi 2011-02-04 00:22:29

5

如果你想这样做,以列维的建议考虑,答案如下:

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

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

1

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

好运。

相关问题