基於資源名的MVC權限控制

  在程序複雜程度不斷上升的過程當中,無可避免須要觸碰到權限控制,而權限控制又與業務邏輯牢牢相關,市場上出現了大量的權限控制產品,而程序的開發,講究去繁化簡的抽象,在個人開發過程當中,逐漸發現程序的權限控制核心不外乎兩個方面:一、資源定位;二、訪問控制列表。本文主要針對資源定位進行分析,並解決一些我所碰見過的問題。而在MVC上,MVC提供給咱們了很是好的訪問控制擴展機制,咱們可以經過這些機制更好地控制系統權限。git

  在咱們以前的開發中,針對ASP.NET下WebForm進行開發,不少人都採用了繼承Page基類自定義BasePage,構造本身的驗證邏輯後再將最終的展現頁面繼承於BasePage,全部的驗證邏輯中,也必須解決我上文中提到的兩個問題:資源定位和訪問控制列表。而WebForm下使用路由機制的機會並很少,意義也並不大,在這個背景下,大多數人產生了這樣的錯覺:使用URL進行資源定位就夠了。我所見過的幾個項目,大多采用了這個辦法,或者這個辦法的細微變種。而WebForm的機制也決定了這個方法是具備必定可行性的,每個資源(頁面)都是一個類,資源定位較爲容易。而進入MVC時代後,一切都發生了改變。在MVC的場景下,你將遇到如下幾個問題:github

  一、  路由機制的引入會出現一個資源多個URL的可能以及增長Area等參數後URL的不肯定性。ajax

  二、  Action重載的問題難以解決。例如一個頁面Order,在Get方法下不帶參訪問是被容許的,而在Post下帶參訪問是被禁止的,但這兩個資源的URL都是 /Order/,在一些第三方庫的輔助下,甚至能有更多的重載Action,這就致使URL定位機制的全面失效。spa

 

  而面對上述狀況,用一種什麼樣的方式進行資源的定位、控制就成爲放在咱們面前的難題。在MVC下,能夠認爲每個方法(Action)即爲一個資源(頁面),用何種方法能在程序外部控制這些資源使得權限控制能對其輕鬆進行,這是整個問題的核心。上文說到,每一個方法即爲一個資源,那咱們是否能夠將方法名、方法簽名做爲資源的定位標識?答案是能夠的。code

  在MVC下,進行權限控制,很天然地想到了使用自定義AuthorizationFilter來進行控制,那麼在這個Attribute中是能夠得到方法相關信息的。orm

var t = (ReflectedActionDescriptor) filterContext.ActionDescriptor;
var method= t.MethodInfo.ToString();

  在派生自FilterAttribute, IAuthorizationFilter的自定義Attribute中,能夠根據上面兩個方法獲取方法的完整簽名,包括返回類型、方法名、參數類型。blog

  經過這個方法是能夠進行權限控制的,可是這個方法存在着一個致命的缺點:返回值的類型名、參數的類型名,有的是徹底限定名,即須要帶命名空間,而有的是不徹底限定名。而這個徹底限定名的獲取是較爲繁瑣的,所以,這個方法的可操做性大大下降。繼承

  那如何優雅地定位到咱們所須要控制的資源呢?咱們是否能夠爲咱們所須要的資源取一個名字,而後在訪問控制列表中將這個名字添加進去,每次執行這個Action的時候獲取當前Action的名字,而後在訪問控制列表中進行比對就能夠解決這個問題。那如何將資源進行命名就成了解決問題的關鍵。代碼級的資源控制必須想到元數據,而須要在元數據中增長信息,這個任務天然又落到Attribute的身上。ip

  咱們自定義一個Attribute爲Action標註資源名稱:資源

  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public sealed class ResourceCustomerNameAttribute : Attribute
    {
        private readonly string _resourceName;

        public ResourceCustomerNameAttribute(string resourceName)
        {
            _resourceName = resourceName;
        }

        public string ResourceName
        {
            get { return _resourceName; }
        }
    }

  這個Attribute實現的功能很簡單:爲資源進行命名(若是方便,使用ID會更高效一點)。

  完成了對資源的命名,接下來須要對資源與訪問控制列表進行比對,我是這樣使用的:繼承FilterAttribute, IAuthorizationFilter,在派生類中先獲取ResourceCustomerNameAttribute實例

  而後用attribute.ResourceName屬性與咱們的訪問控制列表進行比對:

    public void OnAuthorization(AuthorizationContext filterContext)
        {
            var acl = filterContext.HttpContext.Session[SessionName.PermissionSessionName] as IEnumerable<IAcl>;
                
            var attNames = filterContext.ActionDescriptor.GetCustomAttributes(typeof(ResourceCustomerNameAttribute), true) as IEnumerable<ResourceCustomerNameAttribute>;

            var anonymous =filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>;

            if (anonymous != null && anonymous.Any())
            {
                return;
            }
            if (acl == null || !acl.Any())
            {
                filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = _controller, action = _action }));
            }
            else
            {
                var joinResult = (from aclEntity in acl
                                  join attName in attNames on aclEntity.ResourceName equals attName.ResourceName
                                  select attName
                ).Any();

                if (joinResult)
                {
                    return;
                }
                else
                {
                    filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = _controller, action = _action }));
                }
            }
        }

  若是比對成功則獲取相應訪問許可,能夠對資源進行訪問,若比對失敗則跳轉至受權失敗頁面。

  而在實際的使用過程當中,咱們在登陸成功後,將構造用戶的ACL,並將ACL存入Session中,以後用戶在每一次的訪問中均使用Session中的ACL進行比對,受權。

  在這個具體實現中,我有一個難以處理的問題:即用戶訪問頁面時若未受權,則跳轉到未受權提示頁面,若用戶進行基於Json傳遞的ajax調用時如何向用戶提供合適的信息?以前考慮過使用ContentType來判斷,但是後來以爲這會致使開發之中的約定過多(更況且我對不少人對Http協議的瞭解程度並不抱有信心)。不知各位是否有更好的辦法來實現多種錯誤返回信息?望不吝賜教。

  模塊的具體代碼已託管於GitHub:https://github.com/uliian/ULiiAnPermissionControlModule

  功能核心模塊已經上傳,示例正被GFW狂虐~各位稍安勿躁,若有靠譜的全局***方法的同窗也但願悄悄告訴我一下~T.T

  歡迎參考,提出您的寶貴意見。

相關文章
相關標籤/搜索