Asp.net-Mvc

如何創建特定於區域、控制器和操作的自定義 AuthorizeAttribute?

  • February 7, 2013

換句話說,這是一個非常愚蠢的想法嗎?

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

要這樣做。

如果確實需要,可以使用控制器的Type或操作的**MethodInfo來做出安全決策。但是,一切都建立在字元串的基礎上是自找麻煩。請記住,路由值與實際控制器的映射不能保證 1:1。如果您使用路由元組 (a, b, c) 來驗證對 SomeController::SomeAction 的訪問,但有人發現 (a, b’, c) 也執行了相同的操作,則該人可以繞過您的安全機制。

編輯以回應評論:

您可以通過filterContext參數的 ActionDescriptor 屬性訪問控制器的 Type 和操作的 MethodInfo 。這是確定MVC 管道正在處理時真正執行的操作的唯一可靠方法,因為您的查找可能與 MVC 幕後發生的事情不完全匹配。一旦你有了 Type/MethodInfo/whatever,你就可以使用你想要的任何資訊(例如它們的完全限定名稱)來做出安全決定。

作為一個實際的例子,考慮一個帶有控制器 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”)。請記住,區域與路由相關聯,而不是與控制器相關聯。而且由於一個控制器可以被多個路由命中(如果定義匹配),那麼一個控制器也可以在邏輯上與多個區域相關聯。這就是為什麼我們告訴開發人員永遠不要使用路由(或區域或 <location> 標籤,通過擴展)來做出安全決策。

此外,您的類中有一個錯誤,即它是可變的(它在 OnAuthorization 中改變了自己的 Roles 屬性)。動作過濾器屬性必須是不可變的,因為它們可能被部分管道記憶體並重用。根據您的應用程序中聲明此屬性的位置,這會引發定時攻擊,然後惡意站點訪問者可以利用該攻擊來授予自己訪問他希望的任何操作的權限。

有關更多資訊,另請參閱我的回复:

引用自:https://stackoverflow.com/questions/4889541