2011-02-03 122 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"];

好運。

相關問題