Asp.Net MVC-4-過濾器1:認證與受權

基礎

過濾器體現了MVC框架中的Aop思想,雖然這種實現並不完美但在實際的開發過程當中通常也足以知足需求了。session

過濾器分類mvc

依據上篇分析的執行時機的不一樣能夠把過濾器按照實現不一樣的接口分爲下面五類:框架

IAuthenticationFilter 認證和全部IActionFilter執行後(OnAuthentication、OnAuthenticationChallenge)ide

IAuthorizationFilter  受權(OnAuthorization)函數

IActionFilter        Action執行先後的操做(OnActionExecuting、OnActionExecuted)url

IResultFilter        Result的執行先後的操做(OnResultExecuting、OnResultExecuted)spa

IExceptionFilter     處理異常(OnException).net

框架已經提供的實現主要有如下幾種調試

AuthorizeAttribute(實現IAuthorizationFilter)、ChildActionOnlyAttribute(實現IAuthorizationFilter)、ActionFilterAttribute(實現IActionFilter和IResultFilter)、AsyncTimeoutAttribute(繼承ActionFilterAttribute) 、ContentTypeAttribute(繼承ActionFilterAttribute)、CopyAsyncParametersAttribute(繼承ActionFilterAttribute)、WebApiEnabledAttribute(繼承ActionFilterAttribute)、ResetThreadAbortAttribute(繼承ActionFilterAttribute)、HandleErrorAttribute(實現IExceptionFilter)、OutputCacheAttribute(繼承ActionFilterAttribute並實現IExceptionFilter)、Controller(實現全部過濾器接口),對於各類實現的用途你們能夠查看源碼,這裏只講一下Controller,這是全部咱們定義的Controller的基類,所以經過重載Controller的各類過濾器接口的實現就能夠實現過濾器的效果而沒必要使用特性或在GlobalFilters中註冊,這是一種很是方便的作法可是缺乏必定的靈活性。code

過濾器的註冊和獲取有三種方式:特性、Controller、Global。

來看過濾器是如何獲取的,前面分析的ControllerActionInvoker的InvokeAction方法中獲取過濾器的方法是GetFilters,它實質是調用一個委託

    protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        return new FilterInfo(_getFiltersThunk(controllerContext, actionDescriptor));
    }

private Func<ControllerContext, ActionDescriptor, IEnumerable<Filter>> _getFiltersThunk = FilterProviders.Providers.GetFilters;

該委託調用FilterProviders類的一個靜態FilterProviderCollection類型變量Providers的方法GetFilters,咱們能夠發現FilterProviderCollection類型是一個FilterProvider的集合,獲取Filters要經過每個FilterProvider調用其GetFilters方法。

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        ……
        IFilterProvider[] providers = CombinedItems;
        List<Filter> filters = new List<Filter>();
        for (int i = 0; i < providers.Length; i++)
        {
            IFilterProvider provider = providers[i];
            foreach (Filter filter in provider.GetFilters(controllerContext, actionDescriptor))
            {
                filters.Add(filter);
            }
        }
        filters.Sort(_filterComparer);
        if (filters.Count > 1)
        {
            RemoveDuplicates(filters);
        }
        return filters;
    }

那這個集合裏面有哪些FilterProvider呢,從FilterProviders的靜態構造函數中能夠找到答案

    static FilterProviders()
    {
        Providers = new FilterProviderCollection();
        Providers.Add(GlobalFilters.Filters);
        Providers.Add(new FilterAttributeFilterProvider());
        Providers.Add(new ControllerInstanceFilterProvider());
    }

 

第一個是全局的GlobalFilterCollection,它既是一個Filter的集合又實現了IFilterProvider,這是咱們設置全局過濾器的地方,查看這裏過濾器是如何添加的:

    private void AddInternal(object filter, int? order)
    {

        ValidateFilterInstance(filter);
        _filters.Add(new Filter(filter, FilterScope.Global, order));
    }

    private static void ValidateFilterInstance(object instance)
    {

        if (instance != null && !(
            instance is IActionFilter ||
            instance is IAuthorizationFilter ||
            instance is IExceptionFilter ||
            instance is IResultFilter ||
            instance is IAuthenticationFilter))
        {
            throw Error.InvalidOperation(MvcResources.GlobalFilterCollection_UnsupportedFilterInstance,
            typeof(IAuthorizationFilter).FullName,
            typeof(IActionFilter).FullName,
            typeof(IResultFilter).FullName,
            typeof(IExceptionFilter).FullName,
            typeof(IAuthenticationFilter).FullName);
        }
    }

能夠看到首先驗證過濾器是否實現了上面所講的五個接口中的一個,而後再依據此對象建立Filter對象(Filter與真正的過濾器是不一樣的,Filter的Instance屬性能夠看作保存了真正的過濾器,另外的兩個屬性Order和Scope主要用來排序用,這點後面再講)並加到集合中。Filter的構造函數以下:

    public Filter(object instance, FilterScope scope, int? order)
    {
        if (instance == null)
        {
            throw new ArgumentNullException("instance");
        }
        if (order == null)
        {
            IMvcFilter mvcFilter = instance as IMvcFilter;
            if (mvcFilter != null)
            {
                order = mvcFilter.Order;
            }
        }
        Instance = instance;
        Order = order ?? DefaultOrder;
        Scope = scope;
    }

第二個FilterProvider是FilterAttributeFilterProvider,這是經過反射獲取特性從而獲取過濾器的地方。

    public virtual IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        if (controllerContext.Controller != null)
        {
            foreach (FilterAttribute attr in GetControllerAttributes(controllerContext, actionDescriptor))
            {
                yield return new Filter(attr, FilterScope.Controller, order: null);
            }
            foreach (FilterAttribute attr in GetActionAttributes(controllerContext, actionDescriptor))
            {
                yield return new Filter(attr, FilterScope.Action, order: null);
            }
        }            
    }

第三個ControllerInstanceFilterProvider是經過Controller建立過濾器的,或者是Controller自己就是一個過濾器(正如前面所言Controller實現了全部類型的過濾器接口)

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        if (controllerContext.Controller != null)
        {
            yield return new Filter(controllerContext.Controller, FilterScope.First, Int32.MinValue);
        }
    }

最後咱們來分析下Filter類型自己,下面來看看Filter的屬性Scope和Order,這二者都是用來肯定Filter的執行順序的,咱們知道在獲取Filter後而在調用以前會調用filters.Sort(_filterComparer)進行排序,_filterComparer是一個FilterComparer類型的比較器,定義以下。

    private class FilterComparer : IComparer<Filter>
    {
        public int Compare(Filter x, Filter y)
        {
            if (x == null && y == null)
            {
                return 0;
            }
            if (x == null)
            {
                return -1;
            }
            if (y == null)
            {
                return 1;
            }
            if (x.Order < y.Order)
            {
                return -1;
            }
            if (x.Order > y.Order)
            {
                 return 1;
            }
            if (x.Scope < y.Scope)
            {
                return -1;
            }
            if (x.Scope > y.Scope)
            {
                return 1;
            }
            return 0;
        }
    }

代碼邏輯很清晰:根據Order而後根據Scope排序。Order是一個整形值,經過Filter的構造函數咱們可知咱們能夠在IMvcFilter(FilterAttribute和Controller都實現此接口)中設置此Order值,不然Order會經過構造函數來設置(若是是null則設置爲默認的-1)

而FilterScope是一個枚舉類型,三種不一樣的FilterProvider會設置不一樣的FilterScope。

    public enum FilterScope
    {
        First = 0,
        Global = 10,
        Controller = 20,
        Action = 30,
        Last = 100,
    }

注意ActionFilter在調用時會先Reverse,使得最優先Filter實際上是最靠近Action的(OnActionExecuting和OnActionExecuted調用順序相反)。至於ActionFilter以外的其它類型執行順序是如何肯定的經過代碼很容易找出答案。

認證

自MVC4之後,認證和受權就分開來了(符合單一職責原則),前面的篇章分析Action的執行時提到認證是最早執行的,而後是受權。

認證過濾器必須實現接口IAuthenticationFilter,同時若是要做爲一種過濾器特性來使用的話必須繼承FilterAttribute。

先來看一個基本的認證明現,這裏使用了很廣泛的session驗證。

    public class CustomAuthenticationAttribute: FilterAttribute,IAuthenticationFilter
    {
        public void OnAuthentication(AuthenticationContext filterContext)
        {
            var session = filterContext.RequestContext.HttpContext.Session;
            if (session != null && session["user"]!=null && session["roles"]!=null)
            {
                string name = session["user"].ToString();
                string[] roles = session["roles"] as string[];
                filterContext.Principal = new GenericPrincipal(new GenericIdentity(name), roles);
            }
            else
            {
                filterContext.Result = new HttpUnauthorizedResult("no authentication");
            }
        }
        public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
        {
            filterContext.Result = new SessionChallengeResult() { currentResult = filterContext.Result};
        }
    }

    class SessionChallengeResult : ActionResult
    {

        public ActionResult currentResult { set; get; }
 
        public override void ExecuteResult(ControllerContext context)
        {
            currentResult.ExecuteResult(context);
            var rsponse = context.HttpContext.Response;
            if (rsponse.StatusCode == (int)HttpStatusCode.Unauthorized)
            {
                rsponse.Redirect(string.Format("~/{0}/{1}","Account", "Login"));
                rsponse.End();
            }
        }
    }

代碼實現很簡單,只經過獲取session中的user和roles來設置Principal(採用基礎的GenericPrincipal和GenericIdentity類型,也能夠嘗試其它的或自定義實現IPrincipal和IIdentity接口的類型),可是若是session中沒有這些信息則設置HttpUnauthorizedResult,這會終結Action的執行直接轉到OnAuthenticationChallenge。在OnAuthenticationChallenge中咱們經過自定義的SessionChallengeResult類來實現若是是未受權(HttpUnauthorizedResult)的Result執行時跳轉到咱們的登陸頁面。

登陸頁面的實現:

首先實現Controller,這裏只實現了基本的功能用於驗證,若是要實現本身的認證邏輯能夠在Validate中去實現。至於登陸頁面的實現不是重點這裏再也不貼出來,只是必須有一個提交到Login的form,而且至少有name和password兩個輸入。另外這裏用了PRG方式,因爲不是本文的重點也就很少加說明了。

    public class AccountController : Controller
    {
        [HttpGet]
        public ActionResult Login()
        {
            ModelStateDictionary redirectModelState = TempData["tmp_model_state"] as ModelStateDictionary;
            if (redirectModelState != null)
            {
                ModelState.Merge(redirectModelState);
            }
            return View();
        }


        public ActionResult Login(string name, string password)
        {
            if (ModelState.IsValid)
            {
                string[] roles = null;
                if (Validate(name, password, out roles))
                {
                    Session["user"] = name;
                    Session["roles"] = roles;
                    return Redirect("~/Home/Index");
                }
                else
                {
                    ModelState.AddModelError("loginerror", "用戶名或密碼不正確");
                    TempData["tmp_model_state"] = ModelState;
                    return Redirect("Login");
                }
            }
            else
            {
                TempData["tmp_model_state"] = ModelState;
                return Redirect("Login");
            }
        }

        private bool Validate(string user, string password, out string[] roles)
        {
            roles = new string[] {"guest"};
            return true;
        }
    }

註冊特性

咱們採用最簡單的註冊,經過在HomeController上添加屬性[CustomAuthentication]

驗證

再次運行程序,發現已經不能直接進入主頁了,而是來到了登陸頁面,輸入用戶名密碼才能夠進入到主頁。同時能夠調試程序看程序流程是否如你所料。

受權

先來看一個基本的受權實現,這裏咱們繼承了AuthorizeAttribute,通常來講這是一種簡單有效的作法。

    public class CustomAuthorizeAtrribute: AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            var principal = filterContext.HttpContext.User;
            if (principal != null)
            {
                var identity = principal.Identity;
                if (identity != null)
                {
                    bool unAuthorize = string.IsNullOrEmpty(Users) && string.IsNullOrEmpty(Roles);
                    if (unAuthorize)
                    {
                        return;
                    }
                    string[] users = null, roles = null;
                    if (!string.IsNullOrEmpty(Users))
                        users = Users.Split(',');
                    if (!string.IsNullOrEmpty(Roles))
                        roles = Roles.Split(',');
                    if (users != null && !string.IsNullOrEmpty(identity.Name))
                    {
                        foreach (var user in users)
                        {
                            if (string.Compare(identity.Name, user) == 0)
                            {
                                return;
                            }
                        }
                    }
                    if (roles != null)
                    {
                        foreach (var role in roles)
                        {
                            if (principal.IsInRole(role))
                            {
                                return;
                            }
                        }
                    }
                }
            }
            filterContext.Result = new HttpUnauthorizedResult("no authentication");
        }
    }

處理邏輯是很簡單的驗證用戶名和角色是否匹配,值得注意的是增長了多用戶和多角色的支持(以逗號分隔)。在HomeController的Action上加上受權過濾器並設置不一樣的roles,分別訪問這些Acion能夠驗證受權過濾器的做用(前面實現的登陸設置的role都是guest)

    [CustomAuthorize(Roles = "guest,admin")]
    public ActionResult Index()

    [CustomAuthorize(Roles = "admin")]
    public ActionResult About()

    [CustomAuthorize(Roles = "guest")]
    public ActionResult Contact()

另一種實現

經過在Controller或者Action上添加特性來實現過濾器的作法既繁雜又可能致使遺漏,並且須要修改時更是麻煩,下面給你們提供一種基於全局的認證和受權方式做爲一種參考

首先添加認證和受權過濾器,下面是認證的實現類

    public class GlobalAuthenticationFilter: IAuthenticationFilter
    {
        public void OnAuthentication(AuthenticationContext filterContext)
        {
            if (filterContext.IsChildAction)
            {
                return;
            }
            var session = filterContext.RequestContext.HttpContext.Session;
            if (session != null && session["user"] != null && session["roles"] != null)
            {
                string name = session["user"].ToString();
                string[] roles = session["roles"] as string[];
                filterContext.Principal = new GenericPrincipal(new GenericIdentity(name), roles);
            }
            else
            {
                return;
            }
        }

        public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
        {
            filterContext.Result = new SessionChallengeResult() { currentResult = filterContext.Result };
        }
    }

能夠看到與前面認證明現類不一樣的是再也不繼承FilterAttribute(所以不能做爲特性),對於部分試圖(IsChildAction)的請求不做處理,而後在session中不存在user和roles時不做處理,這是爲了咱們可以訪問登陸頁面,而除了登陸頁面以外的訪問控制交由受權來實現。

接下來看受權的實現類

    public class GlobalAuthorizeFilter:IAuthorizationFilter
    {
        if (filterContext.IsChildAction)
        {
            return;
        }
        public String Users { get; set; }
        public String Roles { get; set; }
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            string accountControllerName = "Account";
            string loginActionName = "Login";
            string controllerName = (string)filterContext.RouteData.Values["controller"];
            string actionName = (string)filterContext.RouteData.Values["action"];            
if (string.Compare(accountControllerName, controllerName, true) == 0 && string.Compare(loginActionName, actionName, true) == 0) { return; } var principal = filterContext.HttpContext.User; if (principal != null) { var identity = principal.Identity; if (identity != null) { bool unAuthorize = string.IsNullOrEmpty(Users) && string.IsNullOrEmpty(Roles); if (unAuthorize) { return; } string[] users = null, roles = null; if (!string.IsNullOrEmpty(Users)) users = Users.Split(','); if (!string.IsNullOrEmpty(Roles)) roles = Roles.Split(','); if (users != null && !string.IsNullOrEmpty(identity.Name)) { foreach (var user in users) { if (string.Compare(identity.Name, user) == 0) { return; } } } if (roles != null) { foreach (var role in roles) { if (principal.IsInRole(role)) { return; } } } } } filterContext.Result = new HttpUnauthorizedResult("no authentication"); } }

能夠看到新的受權類再也不繼承AuthorizeAttribute,注意前面幾行代碼的做用就是給登陸頁面放行,而其餘頁面若是未登陸則會重定向到登陸頁面。

最後咱們在FilterConfig中註冊全局過濾器

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new GlobalAuthenticationFilter());
        filters.Add(new GlobalAuthorizeFilter() {Roles = "guest"});
    }

經過運行查看頁面能夠驗證過濾器,這種作法的問題主要在於整個程序的受權只能採用同一種策略,那麼如何實現對不一樣url的不一樣受權方式呢,你們能夠試着實現它(這固然是能夠實現的)。另一個問題是若是有多個受權和認證過濾器的話該如何考慮組合在一塊兒呢,好比咱們可否使用GlobalAuthenticationFilter作認證而組合GlobalAuthorizeFilter和CustomAuthorizeAtrribute作受權呢。最後一個問題:受權和認證過濾器對MVC程序中添加的Asp.net頁面(aspx)有效麼,要如何處理呢。

相關文章
相關標籤/搜索