ASP.NET Identity 简介
ASP.NET 成员资格 (ASP.NET Membership)
在 2005 年,ASP.NET 成员资格用于解决站点在成员资格方面的常见需求。这些需求包括表单身份验证,一个用于存储用户名、密码和用户资料信息 (profile) 的 SQL Server 数据库。而在现如今,对于 Web 应用程序的数据存储,我们拥有了更多的选项。与此同时,大多数程序员希望自己的站点能够使用第三方用户标识提供商提供的身份验证和授权功能。但是,由于 ASP.NET 成员资格自身设计的限制,其已经难以适应这种变化:
数据库架构为 SQL Server 而设计,且无法修改。虽然你可以添加额外的用户资料信息,但这些信息被存入了一张不同的数据表。这使得这些信息难以访问(除了通过 Profile Provider API)。
虽然提供程序系统允许你对后台数据存储结构的修改,但是该系统的设计基于一个假设,即对关系型数据库的使用。虽然你也可以写一个面向非关系型存储机制的提供程序(例如 Windows Azure 存储表),但在那之后,围绕着相关的设计,你还需要大量的工作。这包括编写大量的代码,以及为那些 NoSQL 数据库不支持的方法抛出一大堆的 System.NotImplementedException 异常。
由于“登录/登出”功能基于表单身份验证,因此该成员资格系统无法用于 OWIN。OWIN 包括了一些用于身份验证的中间件。其中有对使用外部身份标识提供商(例如 Microsoft 账户, Facebook, Google, Twitter 等)进行登录的支持;对使用组织账号(来自于组织内部的 Active Directory 或者 Windows Azure Active Directory 等)进行登录的支持。OWIN 同时提供了对 OAuth 2.0, JWT and CORS (Cross-Origin Resource Sharing,跨域资源共享) 的支持。
ASP.NET 简单成员资格 (ASP.NET Simple Membership)
ASP.NET 简单成员资格是为 ASP.NET Web Pages 开发的一套成员资格系统。它与 WebMatrix 和 Visual Studio 2010 SP1 一同发布。其目的是简化为 Web Pages 应用程序添加成员资格功能的过程。
虽然,简单成员资格的确简化了添加用户资料信息的过程,但它依旧存在 ASP.NET 成员资格的其他问题。它也有自身的一些限制:
难于将成员资格系统的数据存储在非关系型存储结构中。
无法将其和 OWIN 一起使用。
它不能很好的和已有的 ASP.NET 成员资格提供程序一起工作,它还是不可扩展的。
ASP.NET 统一提供程序 (ASP.NET Universal Providers)
ASP.NET Universal Providers 能够将成员资格信息存储在 Windows Azure SQL 数据库中,它同时能够和 SQL Server Compact 一起工作。Universal Providers 构建于 Entity Framework Code First 之上,这意味着 Universal Providers 可以将数据存入任何支持实体框架的存储系统。Universal Providers 所使用的数据库架构也得到了极大地简化。
由于 Universal Providers 构建于 ASP.NET 成员资格基础架构之上,因此它也同样具有 SqlMembership 提供程序相同的限制。它们为关系型数据库而设计,难于自定义用户资料和其他用户信息。这些提供程序也依然使用表单身份验证完成“登录/登出”操作。
ASP.NET Identity
在 ASP.NET 成员资格系统不断演化的这些年里,ASP.NET 团队也不断地得到来自用户的大量反馈。
“用户需要在应用程序中注册,之后将会通过使用注册时的用户名和密码进行登录。”这一假设现在已经不在成立。Web 网络已经更加社交化,使得用户可以与其他人通过诸如 Facebook, Twitter 之类的社交渠道实时的交互。开发人员希望用户能够使用它们的社交网络身份标识进行登录,以便在网站上提供更好的用户体验。一个现代的成员资格系统必须能够支持基于跳转的登录操作,用以支持 Facebook, Twitter 等身份验证提供商。
伴随着 Web 开发的不断演化,Web 开发的模式也在不断变化。对应用程序代码的单元测试已经变成应用程序开发人员的主要关注点。在 2008 年,ASP.NET 添加了一个基于“模型-视图-控制器 (MVC)”模式的新框架。该框架的目的之一既是帮助开发人员构建“可单元测试 (unit testable)”的 ASP.NET 应用程序。希望对其应用程序逻辑进行单元测试的开发人员同样希望能够对成员资格系统进行单元测试。
考虑 Web 应用程序开发中的这些变化,ASP.NET 带来了下列特性:
One ASP.NET Identity 系统
ASP.NET Identity 可以用在所有的 ASP.NET 框架上,譬如 ASP.NET MVC, Web Forms, Web Pages, Web API 和 SignalR。
ASP.NET Identity 可以用在各种应用程序中,包括 Web 应用程序、移动应用、商城应用或者混合架构应用。
更容易加入用户的个人资料信息
你能够控制用户及其个人资料信息的全部架构。举例来说,你可以很容易地让系统存储用户在注册时输入的出生日期信息。
持久化控制
默认情况下,ASP.NET Identity 系统将所有的数据存储在数据库中。ASP.NET Identity 使用 Entity Framework Code First 实现其所有的持久化机制。
得益于你对数据库架构的控制,一些常见的任务诸如改变表名称、改变数据类型等都可以很轻易地完成。
能够很容易地引入其他不同的存储机制,例如 SharePoint, Windows Azure 存储表服务, NoSQL 数据库等等。不必再抛出 System.NotImplementedException 异常了。
单元测试能力
ASP.NET Identity 使得 Web 应用程序能够更好地进行单元测试。你可以为你应用程序使用了 ASP.NET Identity 的部分编写单元测试。
角色提供程序
ASP.NET Identity 中的角色提供程序让你可以基于角色来限制对应用程序某个部分的访问。你可以很容易地创建诸如 “Admin” 之类的角色,并将用户加入其中。
基于声明的 (Claims Based)
ASP.NET Identity 支持“基于声明的身份验证 (claims-based authentication)”。这种机制使用一组“声明 (claims)”来表示用户的身份标识。比起角色,“声明”使得开发人员能够更好地描述用户的身份标识。基于声明的成员资格不同于基于角色的成员资格,后者本质上只是一个布尔值(即“属于”或“不属于”特定角色),一个”声明“可以包含更多关于用户标识和成员资格的信息,信息的形式也更加丰富。
社交账号登录提供程序 (Social Login Providers)
你可以很容易地为你的应用程序加入社交账号登录功能(例如 Microsoft 账户, Facebook, Twitter, Google 等),并将用户特定的数据存入你的应用程序。
Windows Azure Active Directory
你还可以加入使用 Windows Azure Active Directory 进行登录的功能,并将用户特定的数据存入你的应用程序。更多信息参见“在 Visual Studio 2013 中 创建 ASP.NET Web 应用程序” 中的 组织账户 (Organizational Accounts) 一节。
OWIN 集成
ASP.NET 授权 (ASP.NET Authentication) 现在基于 OWIN 中间件 (middleware),可以在任何基于 OWIN 的宿主上使用。ASP.NET Identity 没有对 System.Web 的任何依赖,它完全兼容于 OWIN 框架,可以被用在任何由OWIN 承载的应用程序。
ASP.NET Identity 使用 ASP.NET 授权 (ASP.NET Authentication) 为 Web 站点提供对用户的”登录/登出“操作。这意味着应用程序使用 CookieAuthentication 生成 cookie 而非 FormsAuthentication。
NuGet 包
ASP.NET Identity 作为一个 NuGet 包进行发布,并且在 Visual Studio 2013 中作为 ASP.NET MVC, Web Forms 和 Web API 项目模板的一部分提供。你也可以从 NuGet 库中下载到该 NuGet 包。
这种发布方式使得 ASP.NET 团队能够为了添加新功能或者进行 BUG 修复更好的进行迭代,更加敏捷的进行发布给开发人员。
ASP.NET Identity 入门
在 Visual Studio 2013 的若干个项目模板中都内置了 ASP.NET Identity,包括 ASP.NET MVC, Web Forms, Web API 和 SPA。在这个演练中,我们将阐述,这些项目是如何使用 ASP.NET Identity 来添加对用户的“注册”/“登录”/“登出”等功能的。
ASP.NET Identity 按照下列流程实现。这篇文章的目的在于为你提供一个对 ASP.NET Identity 的简单介绍。你可以依照顺序一步步地阅读,也可以只阅读特定的细节部分。对于使用 ASP.NET Identity 创建应用程序更详细的说明,包括使用新的 API 来添加用户、角色和用户资料信息,参见文章结尾的 “后续步骤” 一节。
使用 “个人用户账户” (Inpidual Accounts) 创建一个 ASP.NET MVC 应用程序。你可以将 ASP.NET Identity 使用在 ASP.NET MVC, Web Forms, Web API, SignalR 等框架中,在本文章里,我们从一个 ASP.NET MVC 应用程序开始。
新建的项目包含了下列三个 ASP.NET Identity 包:
Microsoft.AspNet.Identity.EntityFramework
这个包容纳了 ASP.NET Identity 基于 Entity Framework 的实现。它将 ASP.NET Identity 的数据和架构存入 SQL Server。
Microsoft.AspNet.Identity.Core
这个包容纳了 ASP.NET Identity 的核心接口。它可以用来编写 ASP.NET Identity 的其他实现,用以支持其他持久化存储系统,如 Windows Azure 表存储, NoSQL 数据库等等。
Microsoft.AspNet.Identity.OWIN
这个包为 ASP.NET 应用程序提供了将 ASP.NET Identity 引入到 OWIN 身份验证的功能。当你在为应用程序加入登录功能,调用 OWIN Cookie 身份验证中间件来生成 cookie 时,会用到这个包。
创建一个用户。
运行应用程序,然后点击 注册 (Register) 链接创建一个用户。以下图片展示了收集用户名和密码的注册页面。
下列代码的第 8 到第 9 行展示了当用户点击 注册 (Register) 按钮时,Account 控制器的 Register 动作将通过调用 ASP.NET Identity API 创建一个新的用户:
复制代码
1 [HttpPost]
2 [AllowAnonymous]
3 [ValidateAntiForgeryToken]
4 public async Task<ActionResult> Register(RegisterViewModel model)
5 {
6 if (ModelState.IsValid)
7 {
8 var user = new ApplicationUser() { UserName = model.UserName };
9 var result = await UserManager.CreateAsync(user, model.Password);
10 if (result.Succeeded)
11 {
12 await SignInAsync(user, isPersistent: false);
13 return RedirectToAction("Index", "Home");
14 }
15 else
16 {
17 AddErrors(result);
18 }
19 }
20
21 // If we got this far, something failed, redisplay form
22 return View(model);
23 }
复制代码
登录
如果用户创建成功,那么它将通过对 SignInAsync 方法的调用完成登录操作。
复制代码
1 [HttpPost]
2 [AllowAnonymous]
3 [ValidateAntiForgeryToken]
4 public async Task<ActionResult> Register(RegisterViewModel model)
5 {
6 if (ModelState.IsValid)
7 {
8 var user = new ApplicationUser() { UserName = model.UserName };
9 var result = await UserManager.CreateAsync(user, model.Password);
10 if (result.Succeeded)
11 {
12 await SignInAsync(user, isPersistent: false);
13 return RedirectToAction("Index", "Home");
14 }
15 else
16 {
17 AddErrors(result);
18 }
19 }
20
21 // If we got this far, something failed, redisplay form
22 return View(model);
23 }
复制代码
复制代码
1 private async Task SignInAsync(ApplicationUser user, bool isPersistent)
2 {
3 AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
4
5 var identity = await UserManager.CreateIdentityAsync(
6 user, DefaultAuthenticationTypes.ApplicationCookie);
7
8 AuthenticationManager.SignIn(
9 new AuthenticationProperties() {
10 IsPersistent = isPersistent
11 }, identity);
12 }
复制代码
上述代码 SingInAsync 方法中的第 5 到第 6 行生成了一个 ClaimsIdentity。由于 ASP.NET Identity 和 OWIN Cookie 身份验证是基于“声明” (claims) 的系统,所以框架要求应用程序为用户生成一个 ClaimsIdentity。ClaimsIdentity 包含有关于用户的所有声明信息,例如用户所属的角色。你也可以在这个阶段为用户添加更多的声明。
上述代码 SingInAsync 方法中的第 8 到第 11 行完成了用户的登录操作。它使用来自 OWIN 的 AuthenticationManager,调用它的 SignIn 方法,并传入生成的 ClaimsIdentity。
登出。
点击 退出登录 (Log off) 链接,调用 Account 控制器的 LogOff 动作。
复制代码
1 // POST: /Account/LogOff
2 [HttpPost]
3 [ValidateAntiForgeryToken]
4 public ActionResult LogOff()
5 {
6 AuthenticationManager.SignOut();
7 return RedirectToAction("Index", "Home");
8 }
复制代码
上述代码中的第 6 行展示了 OWIN 的 AuthenticationManager.SignOut 方法。该方法类似于在 Web Forms 中, FormsAuthentication 使用的 FormsAuthentication.SignOut 方法。