一、前言
在前一篇文章已经为大家介绍了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中权限管理的发展历程:
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/。这里简单的分析它注册和登录的功能的内部实现。
首先使用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机制来完成对应功能。那我们为什么还要研究其源码实现呢?我觉得有两点:
- 研究源码实现,可以让你对其实现原理有一个深刻的理解,对于分析出现的问题有极大的好处。因为只有你了解其实现原理,写功能模块才能更加自信,处理出现的问题才会比别人快。
- 除了第一点之外,研究源码还有一个重要的作用就是学习源码作者的项目分层和代码分离。在现实生活中,有很多朋友抱怨出现瓶颈了,无法提高,因为平常工作中一般都是去写堆功能的代码,觉得对能力没什么提高。此时你完全可以去研究微软开源的代码,通过研究源码来学习大牛们是如何将项目做到低耦合高内聚的,学习大牛们是如何做到代码分离的。然后再讲学习到的内容应用于工作,相信这样的一个过程下来,你不想提高都不行了。渐渐地你会觉得自己也可以完成一个开源框架。
上面介绍了研究源码的两大作用,那我们从Asp.net Identity内部实现中又学到了什么呢?
通过第四部分的代码分析,Asp.net Identity中注册功能的实现主要分为的4点中,我们可以学到如下几点:
- 关注点的分离。Asp.net Identity注册功能中,将用户输入以及密码加密等代码实现都分离到具体的类中进行实现,而不是将其放在UserManager这个类中。这充分体现关注点分离原则
- 针对接口编程原则。Asp.net Identity内部实现中,都是针对于接口编程,每个类中依赖都是接口,并没有依赖与具体类。从而降低代码之间的耦合。
- 实现了依赖注入。Asp.net Identity具体实现是通过在调用端通过依赖注入的方式进行注入。
- 项目分层架构。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。
所以,如果你觉得你现在的工作得到提高的话,完全不需要去什么群里咨询其他的推荐什么书籍什么,从现在开始就开始研究源码吧。如果不知道研究什么源码的话,完全可以从微软的一些开源代码开始,例如就从Asp.net Identity源码开始,不要担心研究完之后,还是怕不能提高,只要你理解和领悟了,你不想提高都难。另外再推荐大家研究下ABP的实现,我最近就在研究它,希望理解透彻之后,再写一个小的Web框架来巩固自己的研究。到时候也会把自己的一些研究心得分享到这里。
六、总结
到这里,本篇文章的介绍就结束了,希望这篇文章可以帮助朋友对微软的用户权限管理框架有进一步的了解,以及希望哪些想提高的朋友,从现在开始就来和我一起来研究微软的开源框架和ABP框架吧。记得研究之后,分享到这里与大家共享哦。