基础

过滤器体现了MVC框架中的Aop思想,虽然这种实现并不完美但在实际的开发过程中一般也足以满足需求了。

过滤器分类

依据上篇分析的执行时机的不同可以把过滤器按照实现不同的接口分为下面五类:

IAuthenticationFilter 认证和所有IActionFilter执行后(OnAuthentication、OnAuthenticationChallenge)

IAuthorizationFilter  授权(OnAuthorization)

IActionFilter        Action执行前后的操作(OnActionExecuting、OnActionExecuted)

IResultFilter        Result的执行前后的操作(OnResultExecuting、OnResultExecuted)

IExceptionFilter     处理异常(OnException)

框架已经提供的实现主要有以下几种

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中注册,这是一种非常方便的做法但是缺少一定的灵活性。

过滤器的注册和获取有三种方式:特性、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 = ; 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 > )
{
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 ;
}
if (x == null)
{
return -;
}
if (y == null)
{
return ;
}
if (x.Order < y.Order)
{
return -;
}
if (x.Order > y.Order)
{
return ;
}
if (x.Scope < y.Scope)
{
return -;
}
if (x.Scope > y.Scope)
{
return ;
}
return ;
}
}

代码逻辑很清晰:根据Order然后根据Scope排序。Order是一个整形值,通过Filter的构造函数我们可知我们可以在IMvcFilter(FilterAttribute和Controller都实现此接口)中设置此Order值,否则Order会通过构造函数来设置(如果是null则设置为默认的-1)

而FilterScope是一个枚举类型,三种不同的FilterProvider会设置不同的FilterScope。

    public enum FilterScope
{
First = ,
Global = ,
Controller = ,
Action = ,
Last = ,
}

注意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) == )
{
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) == && string.Compare(loginActionName, actionName, true) == )
{
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) == )
{
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)有效么,要如何处理呢。

Asp.Net MVC-4-过滤器1:认证与授权的更多相关文章

  1. ASP.NET MVC - 安全、身份认证、角色授权和ASP.NET Identity

    ASP.NET MVC - 安全.身份认证.角色授权和ASP.NET Identity ASP.NET MVC内置的认证特性 AuthorizeAttribute特性(System.Web.Mvc)( ...

  2. 使用ASP.NET MVC操作过滤器记录日志(转)

    使用ASP.NET MVC操作过滤器记录日志 原文地址:http://www.singingeels.com/Articles/Logging_with_ASPNET_MVC_Action_Filte ...

  3. ASP.NET MVC : Action过滤器(Filtering)

    http://www.cnblogs.com/QLeelulu/archive/2008/03/21/1117092.html ASP.NET MVC : Action过滤器(Filtering) 相 ...

  4. [翻译] 使用ASP.NET MVC操作过滤器记录日志

    [翻译] 使用ASP.NET MVC操作过滤器记录日志 原文地址:http://www.singingeels.com/Articles/Logging_with_ASPNET_MVC_Action_ ...

  5. Asp.net MVC 权限过滤器实现方法的最佳实践

    在项目开发中,为了安全.方便地判断用户是否有访问当前资源(Action)的权限,我们一般通过全局过滤器来实现. Asp.net MVC 页面中常见的权限判断使用过滤器主要在以下几种情况(根据权限判断的 ...

  6. ASP.NET MVC动作过滤器

    ASP.NET MVC提供了4种不同的动作过滤器(Aciton Filter). 1.Authorization Filter 在执行任何Filter或Action之前被执行,用于身份验证 2.Act ...

  7. Asp.net MVC 之过滤器

    整理一下MVC中的几种过滤器,以及每种过滤器是干什么用的 四种过滤器 1.AuthorizationFilter(授权过滤器) 2.ActionFilter(方法过滤器) 3.ResultFilter ...

  8. ASP.NET MVC 系统过滤器、自定义过滤器

    一.系统过滤器使用说明 1.OutputCache过滤器 OutputCache过滤器用于缓存你查询结果,这样可以提高用户体验,也可以减少查询次数.它有以下属性: Duration:缓存的时间,以秒为 ...

  9. Asp.Net MVC在过滤器中使用模型绑定

    废话不多话,直接上代码 1.创建MVC项目,新建一个过滤器类以及使用到的实体类: public class DemoFiltersAttribute : AuthorizeAttribute { pu ...

  10. asp.net mvc 利用过滤器进行网站Meta设置

    过去几年都是用asp.net webform进行开发东西,最近听说过时了,同时webform会产生ViewState(虽然我已经不用ruanat=server的控件好久了 :)),对企业应用无所谓,但 ...

随机推荐

  1. CSDN删除上传资源的办法

    转自网友:http://blog.csdn.net/ssergsw/article/details/12489101 我按照下面的方法一试,果然成功了. 昨天晚上进行测试,上传了一个压缩包和大家分享, ...

  2. lftp的用法

    lftp是Linux下的一个ftp工具,支持ftp, ftps, http, https, hftp, fish, sftp, file, bittorrent等协议(支持https 和 ftps,必 ...

  3. Java 9 揭秘(5. 实现服务)

    Tips做一个终身学习的人. Implementing Services 在这章中,主要介绍如下内容: 什么服务,服务接口,服务提供者: 在 JDK 9之前和在JDK 9中如何实现服务 如何使用Jav ...

  4. [转]AngularJS 之 ng-options指令

    原文地址 一. 基本下拉效果(lable for value in array) 其中select标签中的ng-model属性必须有,其值为选中的对象或属性值. <div ng-controll ...

  5. Java基础语法实例(2)——实习第二天

    来到广州实习的第二天,广州好潮湿,这就是我的感觉,手表里面都开始产生了水雾,这就尴尬了...每天不断的雨.好吧,尽管我很喜欢这里的树,但是我以后应该也不会再来广州了,其实也说不准.想起了<谁动了 ...

  6. Java NIO 学习笔记五 缓冲区补充

    1.缓冲区分配 方法   以 ByteBuffer 为例 (1)使用静态方法 ByteBuffer buffer = ByteBuffer.allocate( 500 ); allocate() 方法 ...

  7. PHP设计模式:工厂方法

    示例代码详见https://github.com/52fhy/design_patterns 工厂方法 工厂方法是针对每一种产品提供一个工厂类.通过不同的工厂实例来创建不同的产品实例. 相比简单工厂, ...

  8. ES6的开发环境搭建

    在搭建es6开发环境之前,先简单介绍一下es6. ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了.它的目标,是使得 Java ...

  9. java自带uuid生成

    java自带uuid生成UUID.randomUUID().toString()

  10. php追加数组

    <?php //追加数组 array_merge_recursive()函数与array_merge()相同,可以将两个或多个数组合并在一起,形成一个联合的数组.两者之间的区别在于,当某个输入数 ...