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的控件好久了 :)),对企业应用无所谓,但 ...
随机推荐
- 如何用phpcms将静态网页生成动态网页?
在前两篇随笔中已经简单介绍了phpcms,那么现在让我们来看一下如何用phpcms将静态网页生成动态网页? 1.在templates文件夹下新建模板文件夹ceshi(名字可以自己随笔起) 2.在ces ...
- ThinkPHP3.2 生成二维码
下面是整合将phpqrcode整合到TP生成二维码就可以解决这个问题了.其实也很简单,使用方法如下:先下载附件解压至ThinkPHP/Extend/Vendor目录,目录不存在自己创建. v ...
- Volley源码分析一
Volley源码分析 虽然在2017年,volley已经是一个逐渐被淘汰的框架,但其代码短小精悍,网络架构设计巧妙,还是有很多值得学习的地方. 第一篇文章,分析了请求队列的代码,请求队列也是我们使用V ...
- 安装gcc提示no acceptable C compiler found in $PATH
安装gcc提示no acceptable C compiler found in $PATH 从所报错可以看出是缺少了c编译器,因为gcc就是c编译器,所以没有安装gcc就没有c编译器. 之所以报这样 ...
- BigDecimal四舍五入使用总结
//BigDecimal四舍五入double f1 = new BigDecimal(1).setScale(2,RoundingMode.HALF_UP).doubleValue();//转化成字符 ...
- 实现自己的.NET Core配置Provider之EF
<10分钟就能学会.NET Core配置>里详细介绍了.NET Core配置的用法,另外我还开源了自定义的配置Provider:EF配置Provider和Yaml配置Provider.本文 ...
- 如何用VMware打开vmdk文件
vmdk文件是一个虚拟机备份文件!你可以在vmware新建一个任何类型的虚拟机,命名为“test”,在“我的文档”找到vmware的虚拟机目录“test”,在"test"目录中可以 ...
- iOS 输入限制之 InputKit
前言 最近接手了两个 O2O 的老项目,其中的 Bug 也不言而喻,单看项目中的布局就有 n 种不同的方式,有用纯代码的,有用 Masonry 的,有用 VFL 的,也有用 Xib 的,更有用代码约束 ...
- ASP.NET WEB API 自定义模型校验过滤器
对外公开WEB接口时,对模型校验是常见的安全常识,常见的写法是在controller中判断ModelState.IsValid,以注册用户API为例. Model: public class Regi ...
- Visiual Studio CLR20r3
问题事件名称: CLR20r3 解决方法: 步骤1:开始-->所有程序-->Microsoft Visual Studio 2012-->Visual Studio To ...