一个完整的ASP.NET的请求中会存在身份验证(Authentication)阶段以及授权(Authorization)阶段,英文单词Authentication和Authorization非常相似,所以很多时候会混淆这两个概念。身份验证(Authentication)的目的是知道“你”是谁,而授权(Authorization)则是当“你”访问一个资源时是否符合访问条件,符合就将访问权限授权给你进行访问,否则拒绝访问。
本文将从以下几点介绍ASP.NET MVC如何使用Identity完成资源访问的限制:
● 资源访问的限制方式
● ASP.NET中的访问限制
● ASP.NET MVC中基的访问限制
● ASP.NET MVC中的用户信息
● ASP.NET Identity用户身份信息填充
● ASP.NET MVC访问限制的实现
● ASP.NET MVC基于用户声明的访问限制及自定义限制
什么是资源?在Web中通过URI(Uniform Resource Identifier,统一资源标识符)来对HTML文档、图片、图像等内容定位,反过来说在Web中HTML文档、图片、图像等内容就是资源,而资源访问的限制就是对在用户通过URI访问Web资源时,判断该用户是否有权限访问该资源,如果有则继续访问,否则拒绝访问。
资源访问有以下几种限制方式:
● 匿名访问限制:所有人都可以访问资源。
● 根据用户名访问限制:指定特定的用户,让其能够或者不能访问资源。
● 根据用户角色访问限制:指定特定角色,让拥有该角色的用户能够访问资源。
● 根据用户声明(Claim)访问限制:指定特定的声明(Claim),让身份信息中含有该声明的用户能够访问资源。
● 使用其它用户信息进行访问限制:根据用户身份的其它信息来判断用户是否能够访问资源。
从上面几点可以看出资源访问的限制或者说授权,实际上就是通过用户信息来判断用户是否有访问资源的权限,而常用的信息是用户名、用户角色以及用户声明。
注:对于授权来说,它处于身份验证的后续阶段,所以可以认为在授权阶段时已经存在用户信息,所以可以直接使用用户信息来完成访问限制。
ASP.NET中通过HTTPModule的方式实现了FileAuthorizationModule以及UrlAuthorizationModule来对用户访问文件以及其它资源进行权限控制,其中UrlAuthorizationModule可以通过在web.config中添加如下配置来通过用户名或者用户角色限制访问:
在ASP.NET中可以通过Forms验证+UrlAuthorizationModule来实现用户身份验证和访问授权,更多信息可参考文档:https://docs.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-security/membership/user-based-authorization-cs
Forms验证+UrlAuthorizationModule的方式是用于基于ASP.NET Web Form的应用程序,而ASP.NET MVC虽然也可以使用Forms验证,但是ASP.NET MVC的授权方式是不一样的,它是通过过滤器的方式实现,下面代码为之前文章中用于限制后台管理页面需要登录的代码:
通过在Controller上使用了一个名为Authorize的特性来实现的,这个特性的定义如下:
它用于当用户访问Controller或Action方法时可以通过用户信息对其访问进行限制。
在Authorize特性的定义中可以看到名为Roles以及Users的属性,其作用就是设置可以访问该资源的用户或者角色:
使用方法如下所示:
通过前面的介绍可以知道用户的授权是根据用户信息来的,无论是基于角色的、用户的、声明的甚至是自定义的,都需要依赖用户信息进行权限判断,那么ASP.NET MVC中到底包含什么用户信息?
1. HttpContextBase与IPrincipal:
首先可以知道的是在ASP.NET中有一个最核心的HTTP上下文对象HttpContextBase,它保存了整个Http请求到响应过程的所有相关数据,其定义如下:
其中就包含了一个名为User的IPrincipal类型:
该类型中的Identity属性就包含了用户的信息:
从接口中可以看到它仅仅包含了用户名、身份验证反射以及是否验证通过三个属性。
注:IPrincipal的实现有多种而本例中使用的是ClaimPrincipal。
2. ClaimsIdentity与AuthenticationTicket:
通过前面的文章分析得知Identity基于Cookie的身份验证方式实际上是对一个AuthenticationTicket对象序列化加密、反序列化解密的过程,而这个AuthenticationTicket就携带了所有用户的信息,在AuthenticationTicket的定义中可以看到两个重要的对象,其中AuthenticationProperties保存了身份验证的会话信息,如过期时间、是否允许刷新等。而另一个ClaimsIdentity属性就是以声明(Claim)的方式实现的用户信息。
ClaimsIdentity的部分定义如下:
其中除了实现IIdentity接口的属性外,还有一个重要的属性是Claims,它用于以声明的方式来保存用户信息,那么Identity是如何完成用户数据填充的?
用户的获取填充主要是在用户登录(注册用户后会自动登录)的时候完成的,因为在后续的请求中Identity仅需通过解析加密后的用户信息字符串即可获得用户信息(注:会存在重新生成刷新该信息的情况,如身份信息的滑动过期等)。
通过前面文章的分析知道了在Identity中用户的登录是通过SignInManager对象完成的以下是用户登录的代码及注册代码:
以下是注册代码,实际上是创建完成用户后执行了登录操作:
注:通过对源码分析,SignInManager.PasswordSignInAsync方法实际上最后也是调用SignInAsync方法完成的登录。
那么SignInAsync到底做了什么?
从代码中可以看到,该方法调用了一个名为CreateUserIdentityAsync的方法,根据其方法名、参数以及返回值类型来看就已经可以确定该方法就是通过用户对象生成上面提到的用于以声明的方式保存用户信息的方法。从它的实现中可以看出它实际上是通过UserManager生成的:
而UserManager又是通过ClaimsIdentityFactory完成的:
ClaimsIdentityFactory.CreateAsync方法的实现:
注:实际上身份信息的刷新也是通过UserManager完成的:
方法的实现仍然是UserManager的CreateIdentityAsync方法:
从实现中可以看出如果UserManager支持角色、声明等功能,它会从数据库中加载对应的信息以声明(Claim)的形式保存在ClaimsIdentity对象中。
在数据库中添加以下数据(为了演示功能直接在数据库中添加角色、声明信息并与用户数据进行关联,如果要开发此功能可基于UserManager完成):
角色信息:
用户声明(Claim):
角色与用户信息关联:
注:由于此处Identity EF与MySQL,对象与表映射存在问题,所以多了一些ID列,暂时不管这个问题,关于Identity与MySQL用法可以参考这篇文档:https://docs.microsoft.com/en-us/aspnet/identity/overview/getting-started/aspnet-identity-using-mysql-storage-with-an-entityframework-mysql-provider
运行程序并登录后,在用户信息(ClaimsIdentity)中可找到添加的角色和声明信息:
上面介绍了用户信息的填充,那么访问的限制实际上就是对用户信息比较而已,下面是Authorize特性的核心方法:
其中核心的三个判断为:
● user.Identity.IsAuthenticated:必须通过身份验证。
● (this._usersSplit.Length <= 0 || this._usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)):特性没有指定用户或者当前用户存在于指定的用户列表中。
● (this._rolesSplit.Length <= 0 || this._rolesSplit.Any(new Func<string, bool>(user.IsInRole)):特性没有指定角色或者当前用户拥有指定的角色。
以上三个条件必须全部符合才能够访问。
ASP.NET MVC中虽然用户信息是基于声明的方式保存的,但是却没有实际的实现,所以需要自己动手实现一个(注:也可以参考ASP.NET Core中的实现https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims)。
实现一个自定义的授权特性(注:之前分析过HttpContext中的User是一个IPrincipal类型,实际上MVC使用的是与ClaimsIdentity对应的ClaimsPrincipal)代码如下,该过滤器只是在原有的授权方式基础上添加了声明的检查:
使用方式如下:
登录后访问上面action得到下面结果,验证通过:
注:需要注意的是以上介绍的授权方式,无论是通过角色、用户名还是声明,它都需要以硬编码的形式写在代码中,换句话说就是在开发时必须确定该功能或者Controller/Action访问需求。但是一些时候也会出现访问需求不确定的情况,访问权限的配置会在运行时通过配置文件或者数据库来动态配置,这样的话自定义的授权过滤器就需要依赖一些业务组件来实现自定义的授权流程。
本章介绍了授权与身份验证的关系以及在ASP.NET中的实现,并详细介绍了ASP.NET MVC中的Identity是如何使用身份验证数据来完成授权的。常见的授权方式一般是基于用户名、角色以及声明,但是它们使用的场景边界是不那么明确的,就是说用什么都行实际情况需要根据需求来看,一般权限控制较简单的使用基于角色的授权即可。但无论基于什么来对用户授权,这些信息都属于用户信息,所以在拓展用户的授权时首先要考虑的是用户的特征信息,其次是用户身份验证时如何获取填充这些信息,最后才是考虑如何使用这些信息来进行授权。
参考:
https://msdn.microsoft.com/en-us/library/wce3kxhd.aspx
https://stackoverflow.com/questions/21645323/what-is-the-claims-in-asp-net-identity
https://www.codeproject.com/Articles/98950/ASP-NET-authentication-and-authorization
本文链接:http://www.cnblogs.com/selimsong/p/7828326.html
ASP.NET没有魔法——目录