# ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
## 一、前言
在前一篇文章已经为大家介绍了OWIN和Katana,有了对他们的了解之后,才能更好地去学习Asp.net Identity,因为Asp.net Identity的实现集成了Owin。其实在Asp.net 2.0的时候,微软已经对用户权限管理进行了实现,其实现为Membership。由于之前的实现有很多限制,所以微软在Asp.net 4.5推出了Asp.net Identity。接下来,本篇文章将详细介绍下Asp.net Identity的实现。
## 二、Asp.net中用户权限管理发展历程
在前面我们已经说过,在Asp.net 2.0的时候,Asp.net中就已经实现了用户权限管理,所以,Asp.net 用户权限管理有其发展历程。下图就是Asp.net中权限管理的发展历程:
![](https://box.kancloud.cn/2016-01-23_56a2eb460e3b5.png)
**ASP.NET Membership**
Asp.net Membership是在2005年的Asp.net 2.0引入的。Membership机制引入了表单验证(Form Authentication),以及一个用于存储用户名、密码和其他用户信息的SQL Server数据库。但它同样存在一些限制:
* 数据库只能使用SQL Server,难以对SQL Server Compact、SQL Azure、NoSQL支持。并且你想为用户表添加额外字段的话,此时你只能创建一个User的附加表。对于开发者来说,不能很好地自定义用户信息。
* 由于Asp.net Membership是基于表单进行验证的,因此无法支持OWIN。
### ASP.NET Simple Membership
Asp.net Simple Membership是对Asp.net Membership的一次改进,它使得你可以更容易自定义用户信息。尽管如此,由于它依然是基于Asp.net Membership之上的,所以它仍然存在以下几点限制:
* 对非关系数据库支持不好。
* 不支持OWIN
* 对于已存在的Asp.net Membership Provider支持的不是很好,不利于扩展。
### ASP.NET Universal Providers
Asp.net Universal Providers解决了前两者的一些问题,例如他支持存储用户在Azure SQL和SQL Server Compact数据库中。并且它基于EF code First实现的,所以它支持EF支持的所有数据库。但由于它依然是基于Asp.net Membreship基础架构实现的,所以仍然有些问题不能很好解决。所以它只解决了前两者的部分问题,其本身还存在一些限制:
* 对非关系数据库支持不好
* 不支持OWIN
## 三、Asp.net Identity 详细介绍
随着互联网的快速发展,从而非关系数据库也层出不穷,但之前的三者权限管理都对非关系数据库支持的不是很好,所以微软必须要实现一种新的权限管理机制,所以在。NET Framework中推出了Asp.net Identity。该套机制解决了之前的所有问题。Asp.net 具有如下特点:
* 可用于ASP.NET所有框架上,包括Asp.net MVC、Asp.net Web Forms、Web Pages、Asp.net Web API和SignalR。
* 可用于各种应用程序,包括Web应用、移动应用,Windows Store应用和混合架构应用。
* 用户信息的自定义
* 存储易于扩展:默认使用EF Code First存储在SQL Server数据库中,但可以很好地扩展到SharePoint、Azure SQL和NoSQL 数据库中。
* 支持单元测试
* 提供了Role Provider,使创建和管理变得简单
* 支持面向Claims的身份验证(即:支持基于声明的身份验证),前面的三者都是基于表单的身份验证。
* 支持社交账号的登录,支持Facebook,Microsoft账户、Twitter,Google、QQ等社交账户。
* 支持Windows Azure Active Directory账号登录功能
* 支持OWIN。
* 通过Nuget发布,这样能让Asp.net 团队更好地修复Bug和迭代新功能,并在第一个时间进行发布。将其与System.Web.dll程序集解耦。
## 四、Asp.net Identity内部实现机制
从上面对Asp.net Identity的介绍可以发现,它确实解决了之前的所有问题,那它是如何做到的呢?要知道其实现机制并不难,因为Asp.net Identity已经开源,我们可以到其站点下载其源码研究即可,其开源地址为:[http://aspnetidentity.codeplex.com/](http://aspnetidentity.codeplex.com/)。这里简单的分析它注册和登录的功能的内部实现。
首先使用VS2013创建一个Asp.net MVC站点,此时网站的用户授权和认证模块的代码的实现VS已经帮我们添加好了,我们只需要找到对应的注册和登录功能对其进行分析,从而明白Asp.net Identity是如何帮完成这两个功能的。
首先,我们找到Accout控制器中注册功能的实现代码:
```
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// 有关如何启用帐户确认和密码重置的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=320771
// 发送包含此链接的电子邮件
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "确认你的帐户", "请通过单击 <a href=\"" + callbackUrl + "\">這裏</a>来确认你的帐户");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// 如果我们进行到这一步时某个地方出错,则重新显示表单
return View(model);
}
```
从上面代码的方法名可以看出,完成用户注册的主要实现在于UserManager.CreateAsync方法上,这个方法实现真是在Asp.net Identity帮我们实现,接下来到我们下载的源码来查看该方法的实现。具体的源码实现如下所示:
```
public virtual async Task<IdentityResult> CreateAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException("user");
}
if (password == null)
{
throw new ArgumentNullException("password");
}
// UpdatePassword对密码进行Hash加密
var result = await UpdatePassword(passwordStore, user, password).WithCurrentCulture();
if (!result.Succeeded)
{
return result;
}
// 注册功能的实现
return await CreateAsync(user).WithCurrentCulture();
}
public virtual async Task<IdentityResult> CreateAsync(TUser user)
{
ThrowIfDisposed();
await UpdateSecurityStampInternal(user).WithCurrentCulture();
var result = await UserValidator.ValidateAsync(user).WithCurrentCulture();
if (!result.Succeeded)
{
return result;
}
if (UserLockoutEnabledByDefault && SupportsUserLockout)
{
await GetUserLockoutStore().SetLockoutEnabledAsync(user, true).WithCurrentCulture();
}
// 调用IUserStore的CreateAsync完成用户注册
await Store.CreateAsync(user).WithCurrentCulture();
return IdentityResult.Success;
}
// UserStore中CreateAsync的实现
public virtual async Task CreateAsync(TUser user)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException("user");
}
// 将实体添加进DbSet<User>集合中
_userStore.Create(user);
// 调用SaveChanges将用户保存到数据库中
await SaveChanges().WithCurrentCulture();
}
```
看到这里是不是豁然开朗了很多的,其实Asp.net Identity内部注册功能的实现,我们完全可以自己来实现。其实现简单的说就是:
1\. 对用户提交的数据进行验证
2\. 对密码进行加密保存
3\. 调用Microsoft.AspNet.Identity.EntityFramework命名空间下的UserStore类的CreateAsync方法将用户进行持久化。
4\. UserStore类中的CreateAsync方法的实现也就是DbSet<User>.Add(entity)和SaveChanges()方法将对象持久化。
到这里还有一个问题,其实上面代码调用的是IUserStore接口中的CreateAsync方法,但具体的IUserStore对象是怎么注入进去的呢?
你带着这个疑惑去Asp.net MVC站点中去寻找其注入代码。此时,你可以发现在Startup.Auth.cs和Start.cs文件中有如下实现:
```
// Startup.cs 文件
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
// Start.Auth.cs文件
public void ConfigureAuth(IAppBuilder app)
{
// 配置数据库上下文、用户管理器和登录管理器,以便为每个请求使用单个实例
// .........
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// ........
}
// IdentityConfig.cs文件
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new **ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));** // ........
}
```
看到上面标注红色的代码了吗,这里就是将UserStore注入的地方。看到这里,你是不是没有任何疑惑了。对于登录功能的实现大家同样可以按照这样的方法去探索。本来想一起分析下的,后面想想,还是留给大家去探索吧。
## 五、从Asp.net Identity内部实现学会项目分层架构
其实,在我们平时工作,只要学会如何使用Asp.net Identity机制来完成对应功能。那我们为什么还要研究其源码实现呢?我觉得有两点:
1. 研究源码实现,可以让你对其实现原理有一个深刻的理解,对于分析出现的问题有极大的好处。因为只有你了解其实现原理,写功能模块才能更加自信,处理出现的问题才会比别人快。
2. 除了第一点之外,研究源码还有一个重要的作用就是学习源码作者的项目分层和代码分离。在现实生活中,有很多朋友抱怨出现瓶颈了,无法提高,因为平常工作中一般都是去写堆功能的代码,觉得对能力没什么提高。此时你完全可以去研究微软开源的代码,通过研究源码来学习大牛们是如何将项目做到低耦合高内聚的,学习大牛们是如何做到代码分离的。然后再讲学习到的内容应用于工作,相信这样的一个过程下来,你不想提高都不行了。渐渐地你会觉得自己也可以完成一个开源框架。
上面介绍了研究源码的两大作用,那我们从Asp.net Identity内部实现中又学到了什么呢?
通过第四部分的代码分析,Asp.net Identity中注册功能的实现主要分为的4点中,我们可以学到如下几点:
1. 关注点的分离。Asp.net Identity注册功能中,将用户输入以及密码加密等代码实现都分离到具体的类中进行实现,而不是将其放在UserManager这个类中。这充分体现关注点分离原则
2. 针对接口编程原则。Asp.net Identity内部实现中,都是针对于接口编程,每个类中依赖都是接口,并没有依赖与具体类。从而降低代码之间的耦合。
3. 实现了依赖注入。Asp.net Identity具体实现是通过在调用端通过依赖注入的方式进行注入。
4. 项目分层架构。Asp.net Identity注册功能。AccountController首先调用UserManager的CreateAsync,而UserManager的CreateAsync又调用了IUserStore中的CreateAsync方法来通过调用EF的DbContext来完成数据的持久化。从这个调用过程和类之间的关系可以看出,这真是领域驱动设计的分层体现。领域驱动设计中设计4层,分别是UI层、应用层、领域层和基础设施层。其中UI层对应的就是AccountController类,应用层对应的就是UserManager类、领域层就是具体的User实体、基础设施层对应的就是IUserStore(准确地说,基础设施层中的仓储对应着IUserStore)。上面对应的项目分层,其实每个层中代码的实现都可以按照这个模式去实现。这点在ABP Web框架中得到了很好的实现:[https://github.com/aspnetboilerplate/aspnetboilerplate](https://github.com/aspnetboilerplate/aspnetboilerplate)。
所以,如果你觉得你现在的工作得到提高的话,完全不需要去什么群里咨询其他的推荐什么书籍什么,从现在开始就开始研究源码吧。如果不知道研究什么源码的话,完全可以从微软的一些开源代码开始,例如就从Asp.net Identity源码开始,不要担心研究完之后,还是怕不能提高,只要你理解和领悟了,你不想提高都难。另外再推荐大家研究下ABP的实现,我最近就在研究它,希望理解透彻之后,再写一个小的Web框架来巩固自己的研究。到时候也会把自己的一些研究心得分享到这里。
## 六、总结
到这里,本篇文章的介绍就结束了,希望这篇文章可以帮助朋友对微软的用户权限管理框架有进一步的了解,以及希望哪些想提高的朋友,从现在开始就来和我一起来研究微软的开源框架和ABP框架吧。记得研究之后,分享到这里与大家共享哦。
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结