Asp.Net MVC-4-过滤器1:认证与授权
基础
过滤器体现了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:认证与授权的更多相关文章
- ASP.NET MVC - 安全、身份认证、角色授权和ASP.NET Identity
ASP.NET MVC - 安全.身份认证.角色授权和ASP.NET Identity ASP.NET MVC内置的认证特性 AuthorizeAttribute特性(System.Web.Mvc)( ...
- 使用ASP.NET MVC操作过滤器记录日志(转)
使用ASP.NET MVC操作过滤器记录日志 原文地址:http://www.singingeels.com/Articles/Logging_with_ASPNET_MVC_Action_Filte ...
- ASP.NET MVC : Action过滤器(Filtering)
http://www.cnblogs.com/QLeelulu/archive/2008/03/21/1117092.html ASP.NET MVC : Action过滤器(Filtering) 相 ...
- [翻译] 使用ASP.NET MVC操作过滤器记录日志
[翻译] 使用ASP.NET MVC操作过滤器记录日志 原文地址:http://www.singingeels.com/Articles/Logging_with_ASPNET_MVC_Action_ ...
- Asp.net MVC 权限过滤器实现方法的最佳实践
在项目开发中,为了安全.方便地判断用户是否有访问当前资源(Action)的权限,我们一般通过全局过滤器来实现. Asp.net MVC 页面中常见的权限判断使用过滤器主要在以下几种情况(根据权限判断的 ...
- ASP.NET MVC动作过滤器
ASP.NET MVC提供了4种不同的动作过滤器(Aciton Filter). 1.Authorization Filter 在执行任何Filter或Action之前被执行,用于身份验证 2.Act ...
- Asp.net MVC 之过滤器
整理一下MVC中的几种过滤器,以及每种过滤器是干什么用的 四种过滤器 1.AuthorizationFilter(授权过滤器) 2.ActionFilter(方法过滤器) 3.ResultFilter ...
- ASP.NET MVC 系统过滤器、自定义过滤器
一.系统过滤器使用说明 1.OutputCache过滤器 OutputCache过滤器用于缓存你查询结果,这样可以提高用户体验,也可以减少查询次数.它有以下属性: Duration:缓存的时间,以秒为 ...
- Asp.Net MVC在过滤器中使用模型绑定
废话不多话,直接上代码 1.创建MVC项目,新建一个过滤器类以及使用到的实体类: public class DemoFiltersAttribute : AuthorizeAttribute { pu ...
- asp.net mvc 利用过滤器进行网站Meta设置
过去几年都是用asp.net webform进行开发东西,最近听说过时了,同时webform会产生ViewState(虽然我已经不用ruanat=server的控件好久了 :)),对企业应用无所谓,但 ...
随机推荐
- ADO.NET中的DataSet和DataReader
ADO.NET提供两个对象用于检索关系型数据并把它存储在内存中,分别是DataSet和DataReader.DataSet提供内存中关系数据的表现--包括表和次序.约束等表间的关系的完整数据集合.Da ...
- DDD理论学习系列(7)-- 值对象
DDD理论学习系列--案例及目录 1.引言 提到值对象,我们可能立马就想到值类型和引用类型.而在C#中,值类型的代表是strut和enum,引用类型的代表是class.interface.delega ...
- 电脑上的windows键突然失灵了,肿么办
windows经常会用到,或许平时感觉不出异常来,偶尔用一次的时候,去发现失灵了,肿么办? 如果只是单纯的弹出开始菜单来,可以按Ctrl+Esc,功能是一样的. 这种情况其实是windows被禁用了, ...
- asp.net web api实现图片点击式图片验证码
现在验证码的形式越来越丰富,今天要实现的是在点击图片中的文字来进行校验的验证码,如图 这种验证码验证是验证鼠标是否选中了图片中文字的位置,以及选择的顺序,产生验证码的时候可以提供一组底图,然后随机获取 ...
- 高考志愿填报:java 软件 程序员 目前的就业现状
大约在17年前,也就是2000年,学计算机专业的学生可以有大部分都进入本专业,并且就业非常容易.哪怕只会office套件,想找个工作也很简单.那时候学计算机就是最热门的行业. 那时候,搞Java的还是 ...
- 【ASP.NET MVC 牛刀小试】 URL Route
例子引入 先看看如下例子,你能完全明白吗? using System; using System.Collections.Generic; using System.Linq; using Syste ...
- 数据的ID名生成新的引用索引树
<?php $arr= [ '0'=>[ "id"=>2, "name"=>"建材", "pid" ...
- Java数据类型在实际开发中的应用一
在前边的博文中,我已经介绍了Java核心的容器IO等,现在我来说一下java中的数据类型.在java中,一切东西皆为对象(这句话意思是java中绝大数情况都用对象),极少数不是对象的,也存在与之对应的 ...
- gradle的安装,配置,构建,研究,初体验......(入职一周研究的第一个大知识点)
(1)Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具.它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置.更 ...
- 快学Scala之继承
## 1. 继承 Scala语言通过 extends 关键字来继承类. 那么继承一个类有什么好处呢? 子类除了拥有继承自超类的方法和字段(即为val(常量), var(变量)所定义的), 还可 ...