基础

过滤器体现了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. comm的用法

    1.comm的功能 对两个排序过的文本文件进行逐行比较基本用法:comm -[123] file1 file2 2.例子 1)显示两个文本文件都有的行#comm -12 file1 file2 2)显 ...

  2. read命令读取用户输入

    read命令用于从终端或文件中读取用户输入,它读取整行输入,如果没有指定名称,读取的行被赋值给内部变量REPLY.read命令常用选项:-a,-p,-s,-t,-n 1.REPLY变量 $readhe ...

  3. Ruby中文乱码问题

    中文乱码问题 解决方法为只要在文件开头加入 # -*- coding: UTF-8 -*-(EMAC写法) 或者 #coding=utf-8 就行了. 源代码文件中,若包含中文编码,则需要注意两点: ...

  4. Java IO学习笔记总结

    Java IO学习笔记总结 前言 前面的八篇文章详细的讲述了Java IO的操作方法,文章列表如下 基本的文件操作 字符流和字节流的操作 InputStreamReader和OutputStreamW ...

  5. js中的事件,内置对象,正则表达式

    [JS中的事件分类] 1.鼠标事件: click/dbclick/mouseover/mouseout/mousemove/mousedown/mouseup 2.键盘事件: keydown: 键盘按 ...

  6. php利用gd实现图片的边框

    <?php //实现两张图片合并 并内图片有一定的边框 $file = 'image/qr_1047.png'; $logo = 'image/logo_1047.jpg'; $code = ' ...

  7. ELK-初识Elasticsearch

    第一篇:初识Elasticsearch 1.安装 Elasticsearch 要求 java8+的环境,推荐使用 Oracle 1.8.0_131版本的JDK.Java JDK的安装此处不做介绍.这里 ...

  8. CYQ.Data V5 分布式自动化缓存设计介绍(二)

    前言: 最近一段时间,开始了<IT连>创业,所以精力和写的文章多数是在分享创业的过程. 而关于本人三大框架CYQ.Data.Aries.Taurus.MVC的相关文章,基本都很少写了. 但 ...

  9. 【原】无脑操作:eclipse + maven搭建SSM框架

    网上看到一些Spring + Spring MVC + MyBatis框架的搭建教程,不是很详细或是时间久远了,自己动手整一个简单无脑的! 0.系统环境 1)Windows 10 企业版 2)JDK ...

  10. IE的hack问题浅谈

    *我一直以为是IE7的专属选择. 开始,我以为是bug ,怎么可能会有+号.一定是哪位同事写错了.找了度娘才知道自己的不足.原来也是可以的. 后来,通过ie8和ie7来调整自己的css.也感觉不用ha ...