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)有效么,要如何处理呢。

相关文章
相关标签/搜索