注:本文是【ASP.NET Identity系列教程】的第三篇。本系列教程详细、完整、深刻地介绍了微软的ASP.NET Identity技术,描述了如何运用ASP.NET Identity实现应用程序的用户管理,以及实现应用程序的认证与受权等相关技术,译者但愿本系列教程能成为掌握ASP.NET Identity技术的一份完整而有价值的资料。读者如果可以按照文章的描述,一边阅读、一边实践、一边理解,定能有意想不到的巨大收获!但愿本系列博文可以获得广大园友的高度推荐。html
In this chapter, I finish my description of ASP.NET Identity by showing you some of the advanced features it offers. I demonstrate how you can extend the database schema by defining custom properties on the user class and how to use database migrations to apply those properties without deleting the data in the ASP.NET Identity database. I also explain how ASP.NET Identity supports the concept of claims and demonstrates how they can be used to flexibly authorize access to action methods. I finish the chapter—and the book—by showing you how ASP.NET Identity makes it easy to authenticate users through third parties. I demonstrate authentication with Google accounts, but ASP.NET Identity has built-in support for Microsoft, Facebook, and Twitter accounts as well. Table 15-1 summarizes this chapter.
本章将完成对ASP.NET Identity的描述,向你展现它所提供的一些高级特性。我将演示,你能够扩展ASP.NET Identity的数据库架构,其办法是在用户类上定义一些自定义属性。也会演示如何使用数据库迁移,这样能够运用自定义属性,而没必要删除ASP.NET Identity数据库中的数据。还会解释ASP.NET Identity如何支持声明(Claims)概念,并演示如何将它们灵活地用来对动做方法进行受权访问。最后向你展现ASP.NET Identity很容易经过第三方部件来认证用户,以此结束本章以及本书。将要演示的是使用Google帐号认证,但ASP.NET Identity对于Microsoft、Facebook以及Twitter帐号,都有内建的支持。表15-1是本章概要。git
Problem 问题 |
Solution 解决方案 |
Listing 清单号 |
---|---|---|
Store additional information about users. 存储用户的附加信息 |
Define custom user properties. 定义自定义用户属性 |
1–3, 8–11 |
Update the database schema without deleting user data. 更新数据库架构而不删除用户数据 |
Perform a database migration. 执行数据库迁移 |
4–7 |
Perform fine-grained authorization. 执行细粒度受权 |
Use claims. 使用声明(Claims) |
12–14 |
Add claims about a user. 添加用户的声明(Claims) |
Use the ClaimsIdentity.AddClaims method. 使用ClaimsIdentity.AddClaims方法 |
15–19 |
Authorize access based on claim values. 基于声明(Claims)值受权访问 |
Create a custom authorization filter attribute. 建立一个自定义的受权过滤器注解属性 |
20–21 |
Authenticate through a third party. 经过第三方认证 |
Install the NuGet package for the authentication provider, redirect requests to that provider, and specify a callback URL that creates the user account. 安装认证提供器的NuGet包,将请求重定向到该提供器,并指定一个建立用户帐号的回调URL。 |
22–25 |
In this chapter, I am going to continue working on the Users project I created in Chapter 13 and enhanced in Chapter 14. No changes to the application are required, but start the application and make sure that there are users in the database. Figure 15-1 shows the state of my database, which contains the users Admin, Alice, Bob, and Joe from the previous chapter. To check the users, start the application and request the /Admin/Index URL and authenticate as the Admin user.
本章打算继续使用第13章建立并在第14章加强的Users项目。对应用程序无需作什么改变,但须要启动应用程序,并确保数据库中有一些用户。图15-1显示了数据库的状态,它含有上一章的用户Admin、Alice、Bob以及Joe。为了检查用户,请启动应用程序,请求/Admin/Index URL,并以Admin用户进行认证。web
Figure 15-1. The initial users in the Identity database
图15-1. Identity数据库中的最初用户数据库
I also need some roles for this chapter. I used the RoleAdmin controller to create roles called Users and Employees and assigned the users to those roles, as described in Table 15-2.
本章还须要一些角色。我用RoleAdmin控制器建立了角色Users和Employees,并为这些角色指定了一些用户,如表15-2所示。express
Role 角色 |
Members 成员 |
---|---|
Users | Alice, Joe |
Employees | Alice, Bob |
Figure 15-2 shows the required role configuration displayed by the RoleAdmin controller.
图15-2显示了由RoleAdmin控制器所显示出来的必要的角色配置。浏览器
Figure 15-2. Configuring the roles required for this chapter
图15-2. 配置本章所需的角色cookie
When I created the AppUser class to represent users in Chapter 13, I noted that the base class defined a basic set of properties to describe the user, such as e-mail address and telephone number. Most applications need to store more information about users, including persistent application preferences and details such as addresses—in short, any data that is useful to running the application and that should last between sessions. In ASP.NET Membership, this was handled through the user profile system, but ASP.NET Identity takes a different approach.
我在第13章建立AppUser类来表示用户时曾作过说明,基类定义了一组描述用户的基本属性,如E-mail地址、电话号码等。大多数应用程序还须要存储用户的更多信息,包括持久化应用程序爱好以及地址等细节——简言之,须要存储对运行应用程序有用而且在各次会话之间应当保持的任何数据。在ASP.NET Membership中,这是经过用户资料(User Profile)系统来处理的,但ASP.NET Identity采起了一种不一样的办法。session
Because the ASP.NET Identity system uses Entity Framework to store its data by default, defining additional user information is just a matter of adding properties to the user class and letting the Code First feature create the database schema required to store them. Table 15-3 puts custom user properties in context.
由于ASP.NET Identity默认是使用Entity Framework来存储其数据的,定义附加的用户信息只不过是给用户类添加属性的事情,而后让Code First特性去建立须要存储它们的数据库架构便可。表15-3描述了自定义用户属性的情形。架构
Question 问题 |
Answer 回答 |
---|---|
What is it? 什么是自定义用户属性? |
Custom user properties allow you to store additional information about your users, including their preferences and settings. 自定义用户属性让你可以存储附加的用户信息,包括他们的爱好和设置。 |
Why should I care? 为什么要关心它? |
A persistent store of settings means that the user doesn’t have to provide the same information each time they log in to the application. 设置的持久化存储意味着,用户没必要每次登陆到应用程序时都提供一样的信息。 |
How is it used by the MVC framework? 在MVC框架中如何使用它? |
This feature isn’t used directly by the MVC framework, but it is available for use in action methods. 此特性不是由MVC框架直接使用的,但它在动做方法中使用是有效的。 |
Listing 15-1 shows how I added a simple property to the AppUser class to represent the city in which the user lives.
清单15-1演示了如何给AppUser类添加一个简单的属性,用以表示用户生活的城市。app
Listing 15-1. Adding a Property in the AppUser.cs File
清单15-1. 在AppUser.cs文件中添加属性
using System; using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models { public enum Cities { LONDON, PARIS, CHICAGO }
public class AppUser : IdentityUser { public Cities City { get; set; } } }
I have defined an enumeration called Cities that defines values for some large cities and added a property called City to the AppUser class. To allow the user to view and edit their City property, I added actions to the Home controller, as shown in Listing 15-2.
这里定义了一个枚举,名称为Cities,它定义了一些大城市的值,另外给AppUser类添加了一个名称为City的属性。为了让用户可以查看和编辑City属性,给Home控制器添加了几个动做方法,如清单15-2所示。
Listing 15-2. Adding Support for Custom User Properties in the HomeController.cs File
清单15-2. 在HomeController.cs文件中添加对自定义属性的支持
using System.Web.Mvc; using System.Collections.Generic; using System.Web; using System.Security.Principal; using System.Threading.Tasks; using Users.Infrastructure; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Users.Models;
namespace Users.Controllers {
public class HomeController : Controller {
[Authorize] public ActionResult Index() { return View(GetData("Index")); }
[Authorize(Roles = "Users")] public ActionResult OtherAction() { return View("Index", GetData("OtherAction")); }
private Dictionary<string, object> GetData(string actionName) { Dictionary<string, object> dict = new Dictionary<string, object>(); dict.Add("Action", actionName); dict.Add("User", HttpContext.User.Identity.Name); dict.Add("Authenticated", HttpContext.User.Identity.IsAuthenticated); dict.Add("Auth Type", HttpContext.User.Identity.AuthenticationType); dict.Add("In Users Role", HttpContext.User.IsInRole("Users")); return dict; }
[Authorize] public ActionResult UserProps() { return View(CurrentUser); }
[Authorize] [HttpPost] public async Task<ActionResult> UserProps(Cities city) { AppUser user = CurrentUser; user.City = city; await UserManager.UpdateAsync(user); return View(user); }
private AppUser CurrentUser { get { return UserManager.FindByName(HttpContext.User.Identity.Name); } }
private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); } } } }
I added a CurrentUser property that uses the AppUserManager class to retrieve an AppUser instance to represent the current user. I pass the AppUser object as the view model object in the GET version of the UserProps action method, and the POST method uses it to update the value of the new City property. Listing 15-3 shows the UserProps.cshtml view, which displays the City property value and contains a form to change it.
我添加了一个CurrentUser属性,它使用AppUserManager类接收了表示当前用户的AppUser实例。在GET版本的UserProps动做方法中,传递了这个AppUser对象做为视图模型。而在POST版的方法中用它更新了City属性的值。清单15-3显示了UserProps.cshtml视图,它显示了City属性的值,并包含一个修改它的表单。
Listing 15-3. The Contents of the UserProps.cshtml File in the Views/Home Folder
清单15-3. Views/Home文件夹中UserProps.cshtml文件的内容
@using Users.Models @model AppUser @{ ViewBag.Title = "UserProps";}
<div class="panel panel-primary"> <div class="panel-heading"> Custom User Properties </div> <table class="table table-striped"> <tr><th>City</th><td>@Model.City</td></tr> </table> </div>
@using (Html.BeginForm()) { <div class="form-group"> <label>City</label> @Html.DropDownListFor(x => x.City, new SelectList(Enum.GetNames(typeof(Cities)))) </div> <button class="btn btn-primary" type="submit">Save</button> }
Caution Don’t start the application when you have created the view. In the sections that follow, I demonstrate how to preserve the contents of the database, and if you start the application now, the ASP.NET Identity users will be deleted.
警告:建立了视图以后不要启动应用程序。在如下小节中,将演示如何保留数据库的内容,若是如今启动应用程序,将会删除ASP.NET Identity的用户。
The default behavior for the Entity Framework Code First feature is to drop the tables in the database and re-create them whenever classes that drive the schema have changed. You saw this in Chapter 14 when I added support for roles: When the application was started, the database was reset, and the user accounts were lost.
Entity Framework Code First特性的默认行为是,一旦修改了派生数据库架构的类,便会删除数据库中的数据表,并从新建立它们。在第14章能够看到这种状况,在我添加角色支持时:当重启应用程序后,数据库被重置,用户帐号也丢失。
Don’t start the application yet, but if you were to do so, you would see a similar effect. Deleting data during development is usually not a problem, but doing so in a production setting is usually disastrous because it deletes all of the real user accounts and causes a panic while the backups are restored. In this section, I am going to demonstrate how to use the database migration feature, which updates a Code First schema in a less brutal manner and preserves the existing data it contains.
不要启动应用程序,但若是你这么作了,会看到相似的效果。在开发期间删除数据没什么问题,但若是在产品设置中这么作了,一般是灾难性的,由于它会删除全部真实的用户帐号,而备份恢复是很痛苦的事。在本小节中,我打算演示如何使用数据库迁移特性,它能以比较温和的方式更新Code First的架构,并保留架构中的已有数据。
The first step is to issue the following command in the Visual Studio Package Manager Console:
第一个步骤是在Visual Studio的“Package Manager Console(包管理器控制台)”中发布如下命令:
Enable-Migrations –EnableAutomaticMigrations
This enables the database migration support and creates a Migrations folder in the Solution Explorer that contains a Configuration.cs class file, the contents of which are shown in Listing 15-4.
它启用了数据库的迁移支持,并在“Solution Explorer(解决方案资源管理器)”建立一个Migrations文件夹,其中含有一个Configuration.cs类文件,内容如清单15-4所示。
Listing 15-4. The Contents of the Configuration.cs File
清单15-4. Configuration.cs文件的内容
namespace Users.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration< Users.Infrastructure.AppIdentityDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Users.Infrastructure.AppIdentityDbContext"; }
protected override void Seed(Users.Infrastructure.AppIdentityDbContext context) { // This method will be called after migrating to the latest version. // 此方法将在迁移到最新版本时调用
// You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // 例如,你可使用DbSet<T>.AddOrUpdate()辅助器方法来避免建立重复的种子数据 // // context.People.AddOrUpdate( // p => p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // } } }
Tip You might be wondering why you are entering a database migration command into the console used to manage NuGet packages. The answer is that the Package Manager Console is really PowerShell, which is a general-purpose tool that is mislabeled by Visual Studio. You can use the console to issue a wide range of helpful commands. See http://go.microsoft.com/fwlink/?LinkID=108518 for details.
提示:你可能会以为奇怪,为何要在管理NuGet包的控制台中输入数据库迁移的命令?答案是“Package Manager Console(包管理控制台)”是真正的PowerShell,这是Visual studio冒用的一个通用工具。你可使用此控制台发送大量的有用命令,详见http://go.microsoft.com/fwlink/?LinkID=108518。
The class will be used to migrate existing content in the database to the new schema, and the Seed method will be called to provide an opportunity to update the existing database records. In Listing 15-5, you can see how I have used the Seed method to set a default value for the new City property I added to the AppUser class. (I have also updated the class file to reflect my usual coding style.)
这个类将用于把数据库中的现有内容迁移到新的数据库架构,Seed方法的调用为更新现有数据库记录提供了机会。在清单15-5中能够看到,我如何用Seed方法为新的City属性设置默认值,City是添加到AppUser类中自定义属性。(为了体现我一向的编码风格,我对这个类文件也进行了更新。)
Listing 15-5. Managing Existing Content in the Configuration.cs File
清单15-5. 在Configuration.cs文件中管理已有内容
using System.Data.Entity.Migrations; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Infrastructure; using Users.Models;
namespace Users.Migrations {
internal sealed class Configuration : DbMigrationsConfiguration<AppIdentityDbContext> {
public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Users.Infrastructure.AppIdentityDbContext"; }
protected override void Seed(AppIdentityDbContext context) {
AppUserManager userMgr = new AppUserManager(new UserStore<AppUser>(context)); AppRoleManager roleMgr = new AppRoleManager(new RoleStore<AppRole>(context));
string roleName = "Administrators"; string userName = "Admin"; string password = "MySecret"; string email = "admin@example.com";
if (!roleMgr.RoleExists(roleName)) { roleMgr.Create(new AppRole(roleName)); }
AppUser user = userMgr.FindByName(userName); if (user == null) { userMgr.Create(new AppUser { UserName = userName, Email = email }, password); user = userMgr.FindByName(userName); }
if (!userMgr.IsInRole(user.Id, roleName)) { userMgr.AddToRole(user.Id, roleName); }
foreach (AppUser dbUser in userMgr.Users) { dbUser.City = Cities.PARIS; } context.SaveChanges(); } } }
You will notice that much of the code that I added to the Seed method is taken from the IdentityDbInit class, which I used to seed the database with an administration user in Chapter 14. This is because the new Configuration class added to support database migrations will replace the seeding function of the IdentityDbInit class, which I’ll update shortly. Aside from ensuring that there is an admin user, the statements in the Seed method that are important are the ones that set the initial value for the City property I added to the AppUser class, as follows:
你可能会注意到,添加到Seed方法中的许多代码取自于IdentityDbInit类,在第14章中我用这个类将管理用户植入了数据库。这是由于这个新添加的、用以支持数据库迁移的Configuration类,将代替IdentityDbInit类的种植功能,我很快便会更新这个类。除了要确保有admin用户以外,在Seed方法中的重要语句是那些为AppUser类的City属性设置初值的语句,以下所示:
... foreach (AppUser dbUser in userMgr.Users) { dbUser.City = Cities.PARIS; } context.SaveChanges(); ...
You don’t have to set a default value for new properties—I just wanted to demonstrate that the Seed method in the Configuration class can be used to update the existing user records in the database.
你不必定要为新属性设置默认值——这里只是想演示Configuration类中的Seed方法,能够用它更新数据库中的已有用户记录。
Caution Be careful when setting values for properties in the Seed method for real projects because the values will be applied every time you change the schema, overriding any values that the user has set since the last schema update was performed. I set the value of the City property just to demonstrate that it can be done.
警告:在用于真实项目的Seed方法中为属性设置值时要当心,由于你每一次修改架构时,都会运用这些值,这会将自执行上一次架构更新以后,用户设置的任何数据覆盖掉。这里设置City属性的值只是为了演示它可以这么作。
The reason that I added the seeding code to the Configuration class is that I need to change the IdentityDbInit class. At present, the IdentityDbInit class is derived from the descriptively named DropCreateDatabaseIfModelChanges<AppIdentityDbContext> class, which, as you might imagine, drops the entire database when the Code First classes change. Listing 15-6 shows the changes I made to the IdentityDbInit class to prevent it from affecting the database.
在Configuration类中添加种植代码的缘由是我须要修改IdentityDbInit类。此时,IdentityDbInit类派生于描述性命名的DropCreateDatabaseIfModelChanges<AppIdentityDbContext> 类,和你相像的同样,它会在Code First类改变时删除整个数据库。清单15-6显示了我对IdentityDbInit类所作的修改,以防止它影响数据库。
Listing 15-6. Preventing Database Schema Changes in the AppIdentityDbContext.cs File
清单15-6. 在AppIdentityDbContext.cs文件是阻止数据库架构变化
using System.Data.Entity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Models; using Microsoft.AspNet.Identity;
namespace Users.Infrastructure { public class AppIdentityDbContext : IdentityDbContext<AppUser> {
public AppIdentityDbContext() : base("IdentityDb") { }
static AppIdentityDbContext() { Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit()); }
public static AppIdentityDbContext Create() { return new AppIdentityDbContext(); } }
public class IdentityDbInit : NullDatabaseInitializer<AppIdentityDbContext> { } }
I have removed the methods defined by the class and changed its base to NullDatabaseInitializer<AppIdentityDbContext> , which prevents the schema from being altered.
我删除了这个类中所定义的方法,并将它的基类改成NullDatabaseInitializer<AppIdentityDbContext> ,它能够防止架构修改。
All that remains is to generate and apply the migration. First, run the following command in the Package Manager Console:
剩下的事情只是生成并运用迁移了。首先,在“Package Manager Console(包管理器控制台)”中执行如下命令:
Add-Migration CityProperty
This creates a new migration called CityProperty (I like my migration names to reflect the changes I made). A class new file will be added to the Migrations folder, and its name reflects the time at which the command was run and the name of the migration. My file is called 201402262244036_CityProperty.cs, for example. The contents of this file contain the details of how Entity Framework will change the database during the migration, as shown in Listing 15-7.
这建立了一个名称为CityProperty的新迁移(我比较喜欢让迁移的名称反映出我所作的修改)。这会在文件夹中添加一个新的类文件,并且其命名会反映出该命令执行的时间以及迁移名称,例如,个人这个文件名称为201402262244036_CityProperty.cs。该文件的内容含有迁移期间Entity Framework修改数据库的细节,如清单15-7所示。
Listing 15-7. The Contents of the 201402262244036_CityProperty.cs File
清单15-7. 201402262244036_CityProperty.cs文件的内容
namespace Users.Migrations { using System; using System.Data.Entity.Migrations;
public partial class Init : DbMigration { public override void Up() { AddColumn("dbo.AspNetUsers", "City", c => c.Int(nullable: false)); }
public override void Down() { DropColumn("dbo.AspNetUsers", "City"); } } }
The Up method describes the changes that have to be made to the schema when the database is upgraded, which in this case means adding a City column to the AspNetUsers table, which is the one that is used to store user records in the ASP.NET Identity database.
Up方法描述了在数据库升级时,须要对架构所作的修改,在这个例子中,意味着要在AspNetUsers数据表中添加City数据列,该数据表是ASP.NET Identity数据库用来存储用户记录的。
The final step is to perform the migration. Without starting the application, run the following command in the Package Manager Console:
最后一步是执行迁移。无需启动应用程序,只需在“Package Manager Console(包管理器控制台)”中运行如下命令便可:
Update-Database –TargetMigration CityProperty
The database schema will be modified, and the code in the Configuration.Seed method will be executed. The existing user accounts will have been preserved and enhanced with a City property (which I set to Paris in the Seed method).
这会修改数据库架构,并执行Configuration.Seed方法中的代码。已有用户帐号会被保留,且加强了City属性(我在Seed方法中已将其设置为“Paris”)。
To test the effect of the migration, start the application, navigate to the /Home/UserProps URL, and authenticate as one of the Identity users (for example, as Alice with the password MySecret). Once authenticated, you will see the current value of the City property for the user and have the opportunity to change it, as shown in Figure 15-3.
为了测试迁移的效果,启动应用程序,导航到/Home/UserProps URL,并以Identity中的用户(例如Alice,口令MySecret)进行认证。一旦已被认证,便会看到该用户City属性的当前值,并能够对其进行修改,如图15-3所示。
Figure 15-3. Displaying and changing a custom user property
图15-3. 显示和个性自定义用户属性
Now that database migrations are set up, I am going to define a further property just to demonstrate how subsequent changes are handled and to show a more useful (and less dangerous) example of using the Configuration.Seed method. Listing 15-8 shows how I added a Country property to the AppUser class.
如今,已经创建了数据库迁移,我打算再定义一个属性,这偏偏演示了如何处理持续不断的修改,也为了演示Configuration.Seed方法更有用(至少无害)的示例。清单15-8显示了我在AppUser类上添加了一个Country属性。
Listing 15-8. Adding Another Property in the AppUserModels.cs File
清单15-8. 在AppUserModels.cs文件中添加另外一个属性
using System; using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models {
public enum Cities { LONDON, PARIS, CHICAGO }
public enum Countries { NONE, UK, FRANCE, USA }
public class AppUser : IdentityUser { public Cities City { get; set; } public Countries Country { get; set; }
public void SetCountryFromCity(Cities city) { switch (city) { case Cities.LONDON: Country = Countries.UK; break; case Cities.PARIS: Country = Countries.FRANCE; break; case Cities.CHICAGO: Country = Countries.USA; break; default: Country = Countries.NONE; break; } } } }
I have added an enumeration to define the country names and a helper method that selects a country value based on the City property. Listing 15-9 shows the change I made to the Configuration class so that the Seed method sets the Country property based on the City, but only if the value of Country is NONE (which it will be for all users when the database is migrated because the Entity Framework sets enumeration columns to the first value).
我已经添加了一个枚举,它定义了国家名称。还添加了一个辅助器方法,它能够根据City属性选择一个国家。清单15-9显示了对Configuration类所作的修改,以使Seed方法根据City设置Country属性,但只当Country为NONE时才进行设置(在迁移数据库时,全部用户都是NONE,由于Entity Framework会将枚举列设置为枚举的第一个值)。
Listing 15-9. Modifying the Database Seed in the Configuration.cs File
清单15-9. 在Configuration.cs文件中修改数据库种子
using System.Data.Entity.Migrations; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Infrastructure; using Users.Models;
namespace Users.Migrations {
internal sealed class Configuration : DbMigrationsConfiguration<AppIdentityDbContext> {
public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Users.Infrastructure.AppIdentityDbContext"; }
protected override void Seed(AppIdentityDbContext context) {
AppUserManager userMgr = new AppUserManager(new UserStore<AppUser>(context)); AppRoleManager roleMgr = new AppRoleManager(new RoleStore<AppRole>(context));
string roleName = "Administrators"; string userName = "Admin"; string password = "MySecret"; string email = "admin@example.com";
if (!roleMgr.RoleExists(roleName)) { roleMgr.Create(new AppRole(roleName)); }
AppUser user = userMgr.FindByName(userName); if (user == null) { userMgr.Create(new AppUser { UserName = userName, Email = email }, password); user = userMgr.FindByName(userName); }
if (!userMgr.IsInRole(user.Id, roleName)) { userMgr.AddToRole(user.Id, roleName); }
foreach (AppUser dbUser in userMgr.Users) { if (dbUser.Country == Countries.NONE) { dbUser.SetCountryFromCity(dbUser.City); } }
context.SaveChanges(); } } }
This kind of seeding is more useful in a real project because it will set a value for the Country property only if one has not already been set—subsequent migrations won’t be affected, and user selections won’t be lost.
这种种植在实际项目中会更有用,由于它只会在Country属性未设置时,才会设置Country属性的值——后继的迁移不会受到影响,所以不会失去用户的选择。
There is no point defining additional user properties if they are not available in the application, so Listing 15-10 shows the change I made to the Views/Home/UserProps.cshtml file to display the value of the Country property.
应用程序中若是没有定义附加属性的地方,则附加属性就没法使用了,所以,清单15-10显示了我对Views/Home/UserProps.cshtml文件的修改,以显示Country属性的值。
Listing 15-10. Displaying an Additional Property in the UserProps.cshtml File
清单15-10. 在UserProps.cshtml文件中显示附加属性
@using Users.Models @model AppUser @{ ViewBag.Title = "UserProps";} <div class="panel panel-primary"> <div class="panel-heading"> Custom User Properties </div> <table class="table table-striped"> <tr><th>City</th><td>@Model.City</td></tr> <tr><th>Country</th><td>@Model.Country</td></tr> </table> </div> @using (Html.BeginForm()) { <div class="form-group"> <label>City</label> @Html.DropDownListFor(x => x.City, new SelectList(Enum.GetNames(typeof(Cities)))) </div> <button class="btn btn-primary" type="submit">Save</button> }
Listing 15-11 shows the corresponding change I made to the Home controller to update the Country property when the City value changes.
为了在City值变化时可以更新Country属性,清单15-11显示了我对Home控制器所作的相应修改。
Listing 15-11. Setting Custom Properties in the HomeController.cs File
清单15-11. 在HomeController.cs文件中设置自定义属性
using System.Web.Mvc; using System.Collections.Generic; using System.Web; using System.Security.Principal; using System.Threading.Tasks; using Users.Infrastructure; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Users.Models;
namespace Users.Controllers {
public class HomeController : Controller { // ...other action methods omitted for brevity... // ...出于简化,这里忽略了其余动做方法...
[Authorize] public ActionResult UserProps() { return View(CurrentUser); }
[Authorize] [HttpPost] public async Task<ActionResult> UserProps(Cities city) { AppUser user = CurrentUser; user.City = city; user.SetCountryFromCity(city); await UserManager.UpdateAsync(user); return View(user); }
// ...properties omitted for brevity... // ...出于简化,这里忽略了一些属性... } }
All that remains is to create and apply a new migration. Enter the following command into the Package Manager Console:
剩下的事情就是建立和运用新的迁移了。在“Package Manager Console(包管理器控制台)”中输入如下命令:
Add-Migration CountryProperty
This will generate another file in the Migrations folder that contains the instruction to add the Country column. To apply the migration, execute the following command:
这将在Migrations文件夹中生成另外一个文件,它含有添加Country数据表列的指令。为了运用迁移,可执行如下命令:
Update-Database –TargetMigration CountryProperty
The migration will be performed, and the value of the Country property will be set based on the value of the existing City property for each user. You can check the new user property by starting the application and authenticating and navigating to the /Home/UserProps URL, as shown in Figure 15-4.
这将执行迁移,Country属性的值将根据每一个用户当前的City属性进行设置。经过启动应用程序,认证并导航到/Home/UserProps URL,即可以查看新的用户属性,如图15-4所示。
Figure 15-4. Creating an additional user property
图15-4. 建立附加用户属性
Tip Although I am focused on the process of upgrading the database, you can also migrate back to a previous version by specifying an earlier migration. Use the –Force argument make changes that cause data loss, such as removing a column.
提示:虽然咱们关注了升级数据库的过程,但你也能够回退到之前的版本,只需指定一个早期的迁移便可。使用-Force参数进行修改,会引发数据丢失,例如删除数据表列。
In older user-management systems, such as ASP.NET Membership, the application was assumed to be the authoritative source of all information about the user, essentially treating the application as a closed world and trusting the data that is contained within it.
在旧的用户管理系统中,例如ASP.NET Membership,应用程序被假设成是用户全部信息的权威来源,本质上将应用程序视为是一个封闭的世界,而且只信任其中所包含的数据。
This is such an ingrained approach to software development that it can be hard to recognize that’s what is happening, but you saw an example of the closed-world technique in Chapter 14 when I authenticated users against the credentials stored in the database and granted access based on the roles associated with those credentials. I did the same thing again in this chapter when I added properties to the user class. Every piece of information that I needed to manage user authentication and authorization came from within my application—and that is a perfectly satisfactory approach for many web applications, which is why I demonstrated these techniques in such depth.
这是软件开发的一种根深蒂固的方法,令人很难认识到这到底意味着什么,第14章你已看到了这种封闭世界技术的例子,根据存储在数据库中的凭据来认证用户,并根据与凭据关联在一块儿的角色来受权访问。本章前述在用户类上添加属性,也作了一样的事情。我管理用户认证与受权所需的每个数据片断都来自于个人应用程序——并且这是许多Web应用程序都至关满意的一种方法,这也是我如此深刻地演示这些技术的缘由。
ASP.NET Identity also supports an alternative approach for dealing with users, which works well when the MVC framework application isn’t the sole source of information about users and which can be used to authorize users in more flexible and fluid ways than traditional roles allow.
ASP.NET Identity还支持另外一种处理用户的办法,当MVC框架的应用程序不是有关用户的惟一信息源时,这种办法会工做得很好,并且可以比传统的角色受权更为灵活且流畅的方式进行受权。
This alternative approach uses claims, and in this section I’ll describe how ASP.NET Identity supports claims-based authorization. Table 15-4 puts claims in context.
这种可选的办法使用了“Claims(声明)”,所以在本小节中,我将描述ASP.NET Identity如何支持“Claims-Based Authorization(基于声明的受权)”。表15-4描述了声明(Claims)的情形。
提示:“Claim”在英文字典中不彻底是“声明”的意思,根据本文的描述,感受把它说成“声明”也不必定合适,因此在以后的译文中基本都写成中英文并用的形式,即“声明(Claims)”。根据表15-4中的声明(Claims)的定义:声明(Claims)是关于用户的一些信息片断。一个用户的信息片断固然有不少,每个信息片断就是一项声明(Claim),用户的全部信息片断合起来就是该用户的声明(Claims)。请读者注意该单词的单复数形式——译者注
Question 问题 |
Answer 答案 |
---|---|
What is it? 什么是声明(Claims)? |
Claims are pieces of information about users that you can use to make authorization decisions. Claims can be obtained from external systems as well as from the local Identity database. 声明(Claims)是关于用户的一些信息片断,能够用它们作出受权决定。声明(Claims)能够从外部系统获取,也能够从本地的Identity数据库获取。 |
Why should I care? 为什么要关心它? |
Claims can be used to flexibly authorize access to action methods. Unlike conventional roles, claims allow access to be driven by the information that describes the user. 声明(Claims)能够用来对动做方法进行灵活的受权访问。与传统的角色不一样,声明(Claims)让访问可以由描述用户的信息进行驱动。 |
How is it used by the MVC framework? 如何在MVC框架中使用它? |
This feature isn’t used directly by the MVC framework, but it is integrated into the standard authorization features, such as the Authorize attribute. 这不是直接由MVC框架使用的特性,但它集成到了标准的受权特性之中,例如Authorize注解属性。 |
Tip you don’t have to use claims in your applications, and as Chapter 14 showed, ASP.NET Identity is perfectly happy providing an application with the authentication and authorization services without any need to understand claims at all.
提示:你在应用程序中不必定要使用声明(Claims),正如第14章所展现的那样,ASP.NET Identity可以为应用程序提供充分的认证与受权服务,而根本不须要理解声明(Claims)。
A claim is a piece of information about the user, along with some information about where the information came from. The easiest way to unpack claims is through some practical demonstrations, without which any discussion becomes too abstract to be truly useful. To get started, I added a Claims controller to the example project, the definition of which you can see in Listing 15-12.
一项声明(Claim)是关于用户的一个信息片断(请注意这个英文单词的单复数形式——译者注),并伴有该片断出自何处的某种信息。揭开声明(Claims)含义最容易的方式是作一些实际演示,任何讨论都会过于抽象根本没有真正的用处。为此,我在示例项目中添加了一个Claims控制器,其定义如清单15-12所示。
Listing 15-12. The Contents of the ClaimsController.cs File
清单15-12. ClaimsController.cs文件的内容
using System.Security.Claims; using System.Web; using System.Web.Mvc;
namespace Users.Controllers { public class ClaimsController : Controller {
[Authorize] public ActionResult Index() { ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity; if (ident == null) { return View("Error", new string[] { "No claims available" }); } else { return View(ident.Claims); } } } }
Tip You may feel a little lost as I define the code for this example. Don’t worry about the details for the moment—just stick with it until you see the output from the action method and view that I define. More than anything else, that will help put claims into perspective.
提示:你或许会对我为此例定义的代码感到有点失望。此刻对此细节没必要着急——只要稍事忍耐,当看到该动做方法和视图的输出便会明白。尤其重要的是,这有助于洞察声明(Claims)。
You can get the claims associated with a user in different ways. One approach is to use the Claims property defined by the user class, but in this example, I have used the HttpContext.User.Identity property to demonstrate the way that ASP.NET Identity is integrated with the rest of the ASP.NET platform. As I explained in Chapter 13, the HttpContext.User.Identity property returns an implementation of the IIdentity interface, which is a ClaimsIdentity object when working using ASP.NET Identity. The ClaimsIdentity class is defined in the System.Security.Claims namespace, and Table 15-5 shows the members it defines that are relevant to this chapter.
能够经过不一样的方式得到与用户相关联的声明(Claims)。方法之一就是使用由用户类定义的Claims属性,但在这个例子中,我使用了HttpContext.User.Identity属性,目的是演示ASP.NET Identity与ASP.NET平台集成的方式(请注意这句话所表示的含义:用户类的Claims属性属于ASP.NET Identity,而HttpContext.User.Identity属性则属于ASP.NET平台。因而可知,ASP.NET Identity已经融合到了ASP.NET平台之中——译者注)。正如第13章所解释的那样,HttpContext.User.Identity属性返回IIdentity的接口实现,当使用ASP.NET Identity时,该实现是一个ClaimsIdentity对象。ClaimsIdentity类是在System.Security.Claims命名空间中定义的,表15-5显示了它所定义的与本章有关的成员。
Name 名称 |
Description 描述 |
---|---|
Claims | Returns an enumeration of Claim objects representing the claims for the user. 返回表示用户声明(Claims)的Claim对象枚举 |
AddClaim(claim) | Adds a claim to the user identity. 给用户添加一个声明(Claim) |
AddClaims(claims) | Adds an enumeration of Claim objects to the user identity. 给用户添加Claim对象的枚举。 |
HasClaim(predicate) | Returns true if the user identity contains a claim that matches the specified predicate. See the “Applying Claims” section for an example predicate. 若是用户含有与指定谓词匹配的声明(Claim)时,返回true。参见“运用声明(Claims)”中的示例谓词 |
RemoveClaim(claim) | Removes a claim from the user identity. 删除用户的声明(Claim)。 |
Other members are available, but the ones in the table are those that are used most often in web applications, for reason that will become obvious as I demonstrate how claims fit into the wider ASP.NET platform.
还有一些可用的其它成员,但表中的这些是在Web应用程序中最经常使用的,随着我演示如何将声明(Claims)融入更宽泛的ASP.NET平台,它们为何最经常使用就很显然了。
In Listing 15-12, I cast the IIdentity implementation to the ClaimsIdentity type and pass the enumeration of Claim objects returned by the ClaimsIdentity.Claims property to the View method. A Claim object represents a single piece of data about the user, and the Claim class defines the properties shown in Table 15-6.
在清单15-12中,我将IIdentity实现转换成了ClaimsIdentity类型,而且给View方法传递了ClaimsIdentity.Claims属性所返回的Claim对象的枚举。Claim对象所示表示的是关于用户的一个单一的数据片断,Claim类定义的属性如表15-6所示。
Name 名称 |
Description 描述 |
---|---|
Issuer | Returns the name of the system that provided the claim 返回提供声明(Claim)的系统名称 |
Subject | Returns the ClaimsIdentity object for the user who the claim refers to 返回声明(Claim)所指用户的ClaimsIdentity对象 |
Type | Returns the type of information that the claim represents 返回声明(Claim)所表示的信息类型 |
Value | Returns the piece of information that the claim represents 返回声明(Claim)所表示的信息片断 |
Listing 15-13 shows the contents of the Index.cshtml file that I created in the Views/Claims folder and that is rendered by the Index action of the Claims controller. The view adds a row to a table for each claim about the user.
清单15-13显示了我在Views/Claims文件夹中建立的Index.cshtml文件的内容,它由Claims控制器中的Index动做方法进行渲染。该视图为用户的每项声明(Claim)添加了一个表格行。
Listing 15-13. The Contents of the Index.cshtml File in the Views/Claims Folder
清单15-13. Views/Claims文件夹中Index.cshtml文件的内容
@using System.Security.Claims @using Users.Infrastructure @model IEnumerable<Claim> @{ ViewBag.Title = "Claims"; }
<div class="panel panel-primary"> <div class="panel-heading"> Claims </div> <table class="table table-striped"> <tr> <th>Subject</th><th>Issuer</th> <th>Type</th><th>Value</th> </tr> @foreach (Claim claim in Model.OrderBy(x => x.Type)) { <tr> <td>@claim.Subject.Name</td> <td>@claim.Issuer</td> <td>@Html.ClaimType(claim.Type)</td> <td>@claim.Value</td> </tr> } </table> </div>
The value of the Claim.Type property is a URI for a Microsoft schema, which isn’t especially useful. The popular schemas are used as the values for fields in the System.Security.Claims.ClaimTypes class, so to make the output from the Index.cshtml view easier to read, I added an HTML helper to the IdentityHelpers.cs file, as shown in Listing 15-14. It is this helper that I use in the Index.cshtml file to format the value of the Claim.Type property.
Claim.Type属性的值是一个微软模式(Microsoft Schema)的URI(统一资源标识符),这是特别有用的。System.Security.Claims.ClaimTypes类中字段的值使用的是流行模式(Popular Schema),所以为了使Index.cshtml视图的输出更易于阅读,我在IdentityHelpers.cs文件中添加了一个HTML辅助器,如清单15-14所示。Index.cshtml文件正是使用这个辅助器格式化了Claim.Type属性的值。
Listing 15-14. Adding a Helper to the IdentityHelpers.cs File
清单15-14. 在IdentityHelpers.cs文件中添加辅助器
using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity.Owin; using System; using System.Linq; using System.Reflection; using System.Security.Claims;
namespace Users.Infrastructure { public static class IdentityHelpers {
public static MvcHtmlString GetUserName(this HtmlHelper html, string id) { AppUserManager mgr = HttpContext.Current.GetOwinContext().GetUserManager<AppUserManager>(); return new MvcHtmlString(mgr.FindByIdAsync(id).Result.UserName); }
public static MvcHtmlString ClaimType(this HtmlHelper html, string claimType) { FieldInfo[] fields = typeof(ClaimTypes).GetFields(); foreach (FieldInfo field in fields) { if (field.GetValue(null).ToString() == claimType) { return new MvcHtmlString(field.Name); } } return new MvcHtmlString(string.Format("{0}", claimType.Split('/', '.').Last())); } } }
Note The helper method isn’t at all efficient because it reflects on the fields of the ClaimType class for each claim that is displayed, but it is sufficient for my purposes in this chapter. You won’t often need to display the claim type in real applications.
注:该辅助器并不是十分有效,由于它只是针对每一个要显示的声明(Claim)映射出ClaimType类的字段,但对我要的目的已经足够了。在实际项目中不会常常须要显示声明(Claim)的类型。
To see why I have created a controller that uses claims without really explaining what they are, start the application, authenticate as the user Alice (with the password MySecret), and request the /Claims/Index URL. Figure 15-5 shows the content that is generated.
为了弄明白我为什么要先建立一个使用声明(Claims)的控制器,而没有真正解释声明(Claims)是什么的缘由,能够启动应用程序,以用户Alice进行认证(其口令是MySecret),并请求/Claims/Index URL。图15-5显示了生成的内容。
Figure 15-5. The output from the Index action of the Claims controller
图15-5. Claims控制器中Index动做的输出
It can be hard to make out the detail in the figure, so I have reproduced the content in Table 15-7.
这可能还难以认识到此图的细节,为此我在表15-7中重列了其内容。
Subject(科目) | Issuer(发行者) | Type(类型) | Value(值) |
---|---|---|---|
Alice | LOCAL AUTHORITY | SecurityStamp | Unique ID |
Alice | LOCAL AUTHORITY | IdentityProvider | ASP.NET Identity |
Alice | LOCAL AUTHORITY | Role | Employees |
Alice | LOCAL AUTHORITY | Role | Users |
Alice | LOCAL AUTHORITY | Name | Alice |
Alice | LOCAL AUTHORITY | NameIdentifier | Alice’s user ID |
The table shows the most important aspect of claims, which is that I have already been using them when I implemented the traditional authentication and authorization features in Chapter 14. You can see that some of the claims relate to user identity (the Name claim is Alice, and the NameIdentifier claim is Alice’s unique user ID in my ASP.NET Identity database).
此表展现了声明(Claims)最重要的方面,这些是我在第14章中实现传统的认证和受权特性时,一直在使用的信息。能够看出,有些声明(Claims)与用户标识有关(Name声明是Alice,NameIdentifier声明是Alice在ASP.NET Identity数据库中的惟一用户ID号)。
Other claims show membership of roles—there are two Role claims in the table, reflecting the fact that Alice is assigned to both the Users and Employees roles. There is also a claim about how Alice has been authenticated: The IdentityProvider is set to ASP.NET Identity.
其余声明(Claims)显示了角色成员——表中有两个Role声明(Claim),体现出Alice被赋予了Users和Employees两个角色这一事实。还有一个是Alice已被认证的声明(Claim):IdentityProvider被设置到了ASP.NET Identity。
The difference when this information is expressed as a set of claims is that you can determine where the data came from. The Issuer property for all the claims shown in the table is set to LOCAL AUTHORITY, which indicates that the user’s identity has been established by the application.
当这种信息被表示成一组声明(Claims)时的差异是,你可以肯定这些数据是从哪里来的。表中所显示的全部声明的Issuer属性(发布者)都被设置到了LOACL AUTHORITY(本地受权),这说明该用户的标识是由应用程序创建的。
So, now that you have seen some example claims, I can more easily describe what a claim is. A claim is any piece of information about a user that is available to the application, including the user’s identity and role memberships. And, as you have seen, the information I have been defining about my users in earlier chapters is automatically made available as claims by ASP.NET Identity.
所以,如今你已经看到了一些声明(Claims)示例,我能够更容易地描述声明(Claim)是什么了。一项声明(Claim)是可用于应用程序中的有关用户的一个信息片断,包括用户的标识以及角色成员等。并且,正如你所看到的,我在前几章定义的关于用户的信息,被ASP.NET Identity自动地做为声明(Claims)了。
Claims are interesting for two reasons. The first reason is that an application can obtain claims from multiple sources, rather than just relying on a local database for information about the user. You will see a real example of this when I show you how to authenticate users through a third-party system in the “Using Third-Party Authentication” section, but for the moment I am going to add a class to the example project that simulates a system that provides claims information. Listing 15-15 shows the contents of the LocationClaimsProvider.cs file that I added to the Infrastructure folder.
声明(Claims)比较有意思的缘由有两个。第一个缘由是应用程序能够从多个来源获取声明(Claims),而不是只能依靠本地数据库关于用户的信息。你将会看到一个实际的示例,在“使用第三方认证”小节中,将演示如何经过第三方系统来认证用户。不过,此刻我只打算在示例项目中添加一个类,用以模拟一个提供声明(Claims)信息的系统。清单15-15显示了我添加到Infrastructure文件夹中LocationClaimsProvider.cs文件的内容。
Listing 15-15. The Contents of the LocationClaimsProvider.cs File
清单15-15. LocationClaimsProvider.cs文件的内容
using System.Collections.Generic; using System.Security.Claims;
namespace Users.Infrastructure {
public static class LocationClaimsProvider {
public static IEnumerable<Claim> GetClaims(ClaimsIdentity user) { List<Claim> claims = new List<Claim>(); if (user.Name.ToLower() == "alice") { claims.Add(CreateClaim(ClaimTypes.PostalCode, "DC 20500")); claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "DC")); } else { claims.Add(CreateClaim(ClaimTypes.PostalCode, "NY 10036")); claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "NY")); } return claims; }
private static Claim CreateClaim(string type, string value) { return new Claim(type, value, ClaimValueTypes.String, "RemoteClaims"); } } }
The GetClaims method takes a ClaimsIdentity argument and uses the Name property to create claims about the user’s ZIP code and state. This class allows me to simulate a system such as a central HR database, which would be the authoritative source of location information about staff, for example.
GetClaims方法以ClaimsIdentity为参数,并使用Name属性建立了关于用户ZIP码(邮政编码)和州府的声明(Claims)。上述这个类使我可以模拟一个诸如中心化的HR数据库(人力资源数据库)之类的系统,它可能会成为全体职员的地点信息的权威数据源。
Claims are associated with the user’s identity during the authentication process, and Listing 15-16 shows the changes I made to the Login action method of the Account controller to call the LocationClaimsProvider class.
在认证过程期间,声明(Claims)是与用户标识关联在一块儿的,清单15-16显示了我对Account控制器中Login动做方法所作的修改,以便调用LocationClaimsProvider类。
Listing 15-16. Associating Claims with a User in the AccountController.cs File
清单15-16. AccountController.cs文件中用户用声明的关联
... [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginModel details, string returnUrl) { if (ModelState.IsValid) { AppUser user = await UserManager.FindAsync(details.Name, details.Password); if (user == null) { ModelState.AddModelError("", "Invalid name or password."); } else { ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(LocationClaimsProvider.GetClaims(ident)); AuthManager.SignOut(); AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident); return Redirect(returnUrl); } } ViewBag.returnUrl = returnUrl; return View(details); } ...
You can see the effect of the location claims by starting the application, authenticating as a user, and requesting the /Claim/Index URL. Figure 15-6 shows the claims for Alice. You may have to sign out and sign back in again to see the change.
为了看看这个地点声明(Claims)的效果,能够启动应用程序,以一个用户进行认证,并请求/Claim/Index URL。图15-6显示了Alice的声明(Claims)。你可能须要退出,而后再次登陆才会看到发生的变化。
Figure 15-6. Defining additional claims for users
图15-6. 定义用户的附加声明
Obtaining claims from multiple locations means that the application doesn’t have to duplicate data that is held elsewhere and allows integration of data from external parties. The Claim.Issuer property tells you where a claim originated from, which helps you judge how accurate the data is likely to be and how much weight you should give the data in your application. Location data obtained from a central HR database is likely to be more accurate and trustworthy than data obtained from an external mailing list provider, for example.
从多个地点获取声明(Claims)意味着应用程序没必要复制其余地方保持的数据,而且可以与外部的数据集成。Claim.Issuer属性(图15-6中的Issuer数据列——译者注)可以告诉你一个声明(Claim)的发源地,这有助于让你判断数据的精确程度,也有助于让你决定这类数据在应用程序中的权重。例如,从中心化的HR数据库获取的地点数据可能要比外部邮件列表提供器获取的数据更为精确和可信。
The second reason that claims are interesting is that you can use them to manage user access to your application more flexibly than with standard roles. The problem with roles is that they are static, and once a user has been assigned to a role, the user remains a member until explicitly removed. This is, for example, how long-term employees of big corporations end up with incredible access to internal systems: They are assigned the roles they require for each new job they get, but the old roles are rarely removed. (The unexpectedly broad systems access sometimes becomes apparent during the investigation into how someone was able to ship the contents of the warehouse to their home address—true story.)
声明(Claims)有意思的第二个缘由是,你能够用它们来管理用户对应用程序的访问,这要比标准的角色管理更为灵活。角色的问题在于它们是静态的,并且一旦用户已经被赋予了一个角色,该用户即是一个成员,直到明确地删除为止。例如,这意味着大公司的长期雇员,对内部系统的访问会十分惊人:他们每次在得到新工做时,都会赋予所需的角色,但旧角色不多被删除。(在调查某人为什么可以将仓库里的东西发往他的家庭地址过程当中发现,有时会出现异常宽泛的系统访问——真实的故事)
Claims can be used to authorize users based directly on the information that is known about them, which ensures that the authorization changes when the data changes. The simplest way to do this is to generate Role claims based on user data that are then used by controllers to restrict access to action methods. Listing 15-17 shows the contents of the ClaimsRoles.cs file that I added to the Infrastructure.
声明(Claims)能够直接根据用户已知的信息对用户进行受权,这可以保证当数据发生变化时,受权也随之而变。此事最简单的作法是根据用户数据来生成Role声明(Claim),而后由控制器用来限制对动做方法的访问。清单15-17显示了我添加到Infrastructure中的ClaimsRoles.cs文件的内容。
Listing 15-17. The Contents of the ClaimsRoles.cs File
清单15-17. ClaimsRoles.cs文件的内容
using System.Collections.Generic; using System.Security.Claims;
namespace Users.Infrastructure { public class ClaimsRoles {
public static IEnumerable<Claim> CreateRolesFromClaims(ClaimsIdentity user) { List<Claim> claims = new List<Claim>(); if (user.HasClaim(x => x.Type == ClaimTypes.StateOrProvince && x.Issuer == "RemoteClaims" && x.Value == "DC") && user.HasClaim(x => x.Type == ClaimTypes.Role && x.Value == "Employees")) { claims.Add(new Claim(ClaimTypes.Role, "DCStaff")); } return claims; } } }
The gnarly looking CreateRolesFromClaims method uses lambda expressions to determine whether the user has a StateOrProvince claim from the RemoteClaims issuer with a value of DC and a Role claim with a value of Employees. If the user has both claims, then a Role claim is returned for the DCStaff role. Listing 15-18 shows how I call the CreateRolesFromClaims method from the Login action in the Account controller.
CreateRolesFromClaims是一个粗糙的考察方法,它使用了Lambda表达式,以检查用户是否具备StateOrProvince声明(Claim),该声明来自于RemoteClaims发行者(Issuer),值为DC。也检查用户是否具备Role声明(Claim),其值为Employees。若是用户这两个声明都有,那么便返回一个DCStaff角色的Role声明。清单15-18显示了如何在Account控制器中的Login动做中调用CreateRolesFromClaims方法。
Listing 15-18. Generating Roles Based on Claims in the AccountController.cs File
清单15-18. 在AccountController.cs中根据声明生成角色
... [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginModel details, string returnUrl) { if (ModelState.IsValid) { AppUser user = await UserManager.FindAsync(details.Name, details.Password); if (user == null) { ModelState.AddModelError("", "Invalid name or password."); } else { ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(LocationClaimsProvider.GetClaims(ident)); ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident)); AuthManager.SignOut(); AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident); return Redirect(returnUrl); } } ViewBag.returnUrl = returnUrl; return View(details); } ...
I can then restrict access to an action method based on membership of the DCStaff role. Listing 15-19 shows a new action method I added to the Claims controller to which I have applied the Authorize attribute.
而后我能够根据DCStaff角色的成员,来限制对一个动做方法的访问。清单15-19显示了在Claims控制器中添加的一个新的动做方法,在该方法上已经运用了Authorize注解属性。
Listing 15-19. Adding a New Action Method to the ClaimsController.cs File
清单15-19. 在ClaimsController.cs文件中添加一个新的动做方法
using System.Security.Claims; using System.Web; using System.Web.Mvc;
namespace Users.Controllers { public class ClaimsController : Controller {
[Authorize] public ActionResult Index() { ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity; if (ident == null) { return View("Error", new string[] { "No claims available" }); } else { return View(ident.Claims); } }
[Authorize(Roles="DCStaff")] public string OtherAction() { return "This is the protected action"; } } }
Users will be able to access OtherAction only if their claims grant them membership to the DCStaff role. Membership of this role is generated dynamically, so a change to the user’s employment status or location information will change their authorization level.
只要用户的声明(Claims)认可他们是DCStaff角色的成员,那么他们便能访问OtherAction动做。该角色的成员是动态生成的,所以,如果用户的雇用状态或地点信息发生变化,也会改变他们的受权等级。
提示:请读者从这个例子中吸收其中的思想精髓。对于读物的理解程度,仁者见仁,智者见智,能领悟多少,全凭各人,译者感受这里的思想有无数的可能。举例说明:(1)能够根据用户的身份进行受权,好比学生在校时是“学生”,毕业后即是“校友”;(2)能够根据用户所处的部门进行受权,人事部用户属于人事团队,销售部用户属于销售团队,各团队有其本身的应用;(3)下一小节的示例是根据用户的地点受权。简言之:一方面用户的各类声明(Claim)均可以用来进行受权;另外一方面用户的声明(Claim)又是能够自定义的。因而可能的运用就没法估计了。总之一句话,这种基于声明的受权(Claims-Based Authorization)有无限可能!要是没有我这里的提示,是否全部读者在此处都会有所体会?——译者注
The previous example is an effective demonstration of how claims can be used to keep authorizations fresh and accurate, but it is a little indirect because I generate roles based on claims data and then enforce my authorization policy based on the membership of that role. A more direct and flexible approach is to enforce authorization directly by creating a custom authorization filter attribute. Listing 15-20 shows the contents of the ClaimsAccessAttribute.cs file, which I added to the Infrastructure folder and used to create such a filter.
前面的示例有效地演示了如何用声明(Claims)来保持新鲜和准确的受权,但有点不太直接,由于我要根据声明(Claims)数据来生成了角色,而后强制个人受权策略基于角色成员。一个更直接且灵活的办法是直接强制受权,其作法是建立一个自定义的受权过滤器注解属性。清单15-20演示了ClaimsAccessAttribute.cs文件的内容,我将它添加在Infrastructure文件夹中,并用它建立了这种过滤器。
Listing 15-20. The Contents of the ClaimsAccessAttribute.cs File
清单15-20. ClaimsAccessAttribute.cs文件的内容
using System.Security.Claims; using System.Web; using System.Web.Mvc;
namespace Users.Infrastructure { public class ClaimsAccessAttribute : AuthorizeAttribute {
public string Issuer { get; set; } public string ClaimType { get; set; } public string Value { get; set; }
protected override bool AuthorizeCore(HttpContextBase context) { return context.User.Identity.IsAuthenticated && context.User.Identity is ClaimsIdentity && ((ClaimsIdentity)context.User.Identity).HasClaim(x => x.Issuer == Issuer && x.Type == ClaimType && x.Value == Value ); } } }
The attribute I have defined is derived from the AuthorizeAttribute class, which makes it easy to create custom authorization policies in MVC framework applications by overriding the AuthorizeCore method. My implementation grants access if the user is authenticated, the IIdentity implementation is an instance of ClaimsIdentity, and the user has a claim with the issuer, type, and value matching the class properties. Listing 15-21 shows how I applied the attribute to the Claims controller to authorize access to the OtherAction method based on one of the location claims created by the LocationClaimsProvider class.
我所定义的这个注解属性派生于AuthorizeAttribute类,经过重写AuthorizeCore方法,很容易在MVC框架应用程序中建立自定义的受权策略。在这个实现中,若用户是已认证的、其IIdentity实现是一个ClaimsIdentity实例,并且该用户有一个带有issuer、type以及value的声明(Claim),它们与这个类的属性是匹配的,则该用户即是容许访问的。清单15-21显示了如何将这个注解属性运用于Claims控制器,以便根据LocationClaimsProvider类建立的地点声明(Claim),对OtherAction方法进行受权访问。
Listing 15-21. Performing Authorization on Claims in the ClaimsController.cs File
清单15-21. 在ClaimsController.cs文件中执行基于声明的受权
using System.Security.Claims; using System.Web; using System.Web.Mvc; using Users.Infrastructure;
namespace Users.Controllers { public class ClaimsController : Controller {
[Authorize] public ActionResult Index() { ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity; if (ident == null) { return View("Error", new string[] { "No claims available" }); } else { return View(ident.Claims); } } [ClaimsAccess(Issuer="RemoteClaims", ClaimType=ClaimTypes.PostalCode, Value="DC 20500")] public string OtherAction() { return "This is the protected action"; } } }
My authorization filter ensures that only users whose location claims specify a ZIP code of DC 20500 can invoke the OtherAction method.
这个受权过滤器可以确保只有地点声明(Claim)的邮编为DC 20500的用户才能请求OtherAction方法。
One of the benefits of a claims-based system such as ASP.NET Identity is that any of the claims can come from an external system, even those that identify the user to the application. This means that other systems can authenticate users on behalf of the application, and ASP.NET Identity builds on this idea to make it simple and easy to add support for authenticating users through third parties such as Microsoft, Google, Facebook, and Twitter.
基于声明的系统,如ASP.NET Identity,的好处之一是任何声明均可以来自于外部系统,即便是将用户标识到应用程序的那些声明。这意味着其余系统能够表明应用程序来认证用户,而ASP.NET Identity就创建在这样的思想之上,使之可以简单而方便地添加第三方认证用户的支持,如微软、Google、Facebook、Twitter等。
There are some substantial benefits of using third-party authentication: Many users will already have an account, users can elect to use two-factor authentication, and you don’t have to manage user credentials in the application. In the sections that follow, I’ll show you how to set up and use third-party authentication for Google users, which Table 15-8 puts into context.
使用第三方认证有一些实际的好处:许多用户已经有了帐号、用户能够选择使用双因子认证、你没必要在应用程序中管理用户凭据等等。在如下小节中,我将演示如何为Google用户创建并使用第三方认证,表15-8描述了事情的情形。
Question 问题 |
Answer 回答 |
---|---|
What is it? 什么是第三方认证? |
Authenticating with third parties lets you take advantage of the popularity of companies such as Google and Facebook. 第三方认证使你可以利用流行公司,如Google和Facebook,的优点。 |
Why should I care? 为什么要关心它? |
Users don’t like having to remember passwords for many different sites. Using a provider with large-scale adoption can make your application more appealing to users of the provider’s services. 用户不喜欢记住许多不一样网站的口令。使用大范围适应的提供器可以使你的应用程序更吸引有提供器服务的用户。 |
How is it used by the MVC framework? 如何在MVC框架中使用它? |
This feature isn’t used directly by the MVC framework. 这不是一个直接由MVC框架使用的特性。 |
Note The reason I have chosen to demonstrate Google authentication is that it is the only option that doesn’t require me to register my application with the authentication service. You can get details of the registration processes required at http://bit.ly/1cqLTrE.
提示:我选择演示Google认证的缘由是,它是惟一不须要在其认证服务中注册我应用程序的公司。有关认证服务注册过程的细节,请参阅http://bit.ly/1cqLTrE。
ASP.NET Identity comes with built-in support for authenticating users through their Microsoft, Google, Facebook, and Twitter accounts as well more general support for any authentication service that supports OAuth. The first step is to add the NuGet package that includes the Google-specific additions for ASP.NET Identity. Enter the following command into the Package Manager Console:
ASP.NET Identity带有经过Microsoft、Google、Facebook以及Twitter帐号认证用户的内建支持,而且对于支持OAuth的认证服务具备更广泛的支持。第一个步骤是添加NuGet包,包中含有用于ASP.NET Identity的Google专用附件。请在“Package Manager Console(包管理器控制台)”中输入如下命令:
Install-Package Microsoft.Owin.Security.Google -version 2.0.2
There are NuGet packages for each of the services that ASP.NET Identity supports, as described in Table 15-9.
对于ASP.NET Identity支持的每一种服务都有相应的NuGet包,如表15-9所示。
Name 名称 |
Description 描述 |
---|---|
Microsoft.Owin.Security.Google | Authenticates users with Google accounts 用Google帐号认证用户 |
Microsoft.Owin.Security.Facebook | Authenticates users with Facebook accounts 用Facebook帐号认证用户 |
Microsoft.Owin.Security.Twitter | Authenticates users with Twitter accounts 用Twitter帐号认证用户 |
Microsoft.Owin.Security.MicrosoftAccount | Authenticates users with Microsoft accounts 用Microsoft帐号认证用户 |
Microsoft.Owin.Security.OAuth | Authenticates users against any OAuth 2.0 service 根据任一OAuth 2.0服务认证用户 |
Once the package is installed, I enable support for the authentication service in the OWIN startup class, which is defined in the App_Start/IdentityConfig.cs file in the example project. Listing 15-22 shows the change that I have made.
一旦安装了这个包,即可以在OWIN启动类中启用此项认证服务的支持,启动类的定义在示例项目的App_Start/IdentityConfig.cs文件中。清单15-22显示了所作的修改。
Listing 15-22. Enabling Google Authentication in the IdentityConfig.cs File
清单15-22. 在IdentityConfig.cs文件中启用Google认证
using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; using Users.Infrastructure; using Microsoft.Owin.Security.Google;
namespace Users { public class IdentityConfig { public void Configuration(IAppBuilder app) {
app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create); app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), });
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); app.UseGoogleAuthentication(); } } }
Each of the packages that I listed in Table 15-9 contains an extension method that enables the corresponding service. The extension method for the Google service is called UseGoogleAuthentication, and it is called on the IAppBuilder implementation that is passed to the Configuration method.
表15-9所列的每一个包都含有启用相应服务的扩展方法。用于Google服务的扩展方法名称为UseGoogleAuthentication,它经过传递给Configuration方法的IAppBuilder实现进行调用。
Next I added a button to the Views/Account/Login.cshtml file, which allows users to log in via Google. You can see the change in Listing 15-23.
下一步骤是在Views/Account/Login.cshtml文件中添加一个按钮,让用户可以经过Google进行登陆。所作的修改如清单15-23所示。
Listing 15-23. Adding a Google Login Button to the Login.cshtml File
清单15-23. 在Login.cshtml文件中添加Google登陆按钮
@model Users.Models.LoginModel @{ ViewBag.Title = "Login";} <h2>Log In</h2>
@Html.ValidationSummary()
@using (Html.BeginForm()) { @Html.AntiForgeryToken(); <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" /> <div class="form-group"> <label>Name</label> @Html.TextBoxFor(x => x.Name, new { @class = "form-control" }) </div> <div class="form-group"> <label>Password</label> @Html.PasswordFor(x => x.Password, new { @class = "form-control" }) </div> <button class="btn btn-primary" type="submit">Log In</button> }
@using (Html.BeginForm("GoogleLogin", "Account")) { <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" /> <button class="btn btn-primary" type="submit">Log In via Google</button> }
The new button submits a form that targets the GoogleLogin action on the Account controller. You can see this method—and the other changes I made the controller—in Listing 15-24.
新按钮递交一个表单,目标是Account控制器中的GoogleLogin动做。可从清单15-24中看到该方法,以及在控制器中所作的其余修改。
Listing 15-24. Adding Support for Google Authentication to the AccountController.cs File
清单15-24. 在AccountController.cs文件中添加Google认证支持
using System.Threading.Tasks; using System.Web.Mvc; using Users.Models; using Microsoft.Owin.Security; using System.Security.Claims; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Users.Infrastructure; using System.Web;
namespace Users.Controllers {
[Authorize] public class AccountController : Controller {
[AllowAnonymous] public ActionResult Login(string returnUrl) { if (HttpContext.User.Identity.IsAuthenticated) { return View("Error", new string[] { "Access Denied" }); } ViewBag.returnUrl = returnUrl; return View(); }
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginModel details, string returnUrl) { if (ModelState.IsValid) { AppUser user = await UserManager.FindAsync(details.Name, details.Password); if (user == null) { ModelState.AddModelError("", "Invalid name or password."); } else { ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
ident.AddClaims(LocationClaimsProvider.GetClaims(ident)); ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident));
AuthManager.SignOut(); AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident); return Redirect(returnUrl); } } ViewBag.returnUrl = returnUrl; return View(details); }
[HttpPost] [AllowAnonymous] public ActionResult GoogleLogin(string returnUrl) { var properties = new AuthenticationProperties { RedirectUri = Url.Action("GoogleLoginCallback", new { returnUrl = returnUrl}) }; HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google"); return new HttpUnauthorizedResult(); }
[AllowAnonymous] public async Task<ActionResult> GoogleLoginCallback(string returnUrl) { ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync(); AppUser user = await UserManager.FindAsync(loginInfo.Login); if (user == null) { user = new AppUser { Email = loginInfo.Email, UserName = loginInfo.DefaultUserName, City = Cities.LONDON, Country = Countries.UK }; IdentityResult result = await UserManager.CreateAsync(user); if (!result.Succeeded) { return View("Error", result.Errors); } else { result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login); if (!result.Succeeded) { return View("Error", result.Errors); } } }
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(loginInfo.ExternalIdentity.Claims); AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident); return Redirect(returnUrl ?? "/"); }
[Authorize] public ActionResult Logout() { AuthManager.SignOut(); return RedirectToAction("Index", "Home"); }
private IAuthenticationManager AuthManager { get { return HttpContext.GetOwinContext().Authentication; } }
private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); } } } }
The GoogleLogin method creates an instance of the AuthenticationProperties class and sets the RedirectUri property to a URL that targets the GoogleLoginCallback action in the same controller. The next part is a magic phrase that causes ASP.NET Identity to respond to an unauthorized error by redirecting the user to the Google authentication page, rather than the one defined by the application:
GoogleLogin方法建立了AuthenticationProperties类的一个实例,并为RedirectUri属性设置了一个URL,其目标为同一控制器中的GoogleLoginCallback动做。下一个部分是一个神奇阶段,经过将用户重定向到Google认证页面,而不是应用程序所定义的认证页面,让ASP.NET Identity对未受权的错误进行响应:
... HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google"); return new HttpUnauthorizedResult(); ...
This means that when the user clicks the Log In via Google button, their browser is redirected to the Google authentication service and then redirected back to the GoogleLoginCallback action method once they are authenticated.
这意味着,当用户经过点击Google按钮进行登陆时,浏览器被重定向到Google的认证服务,一旦在那里认证以后,便被重定向回GoogleLoginCallback动做方法。
I get details of the external login by calling the GetExternalLoginInfoAsync of the IAuthenticationManager implementation, like this:
我经过调用IAuthenticationManager实现的GetExternalLoginInfoAsync方法,我得到了外部登陆的细节,以下所示:
... ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync(); ...
The ExternalLoginInfo class defines the properties shown in Table 15-10.
ExternalLoginInfo类定义的属性如表15-10所示:
Name 名称 |
Description 描述 |
---|---|
DefaultUserName | Returns the username 返回用户名 |
Returns the e-mail address 返回E-mail地址 |
|
ExternalIdentity | Returns a ClaimsIdentity that identities the user 返回标识该用户的ClaimsIdentity |
Login | Returns a UserLoginInfo that describes the external login 返回描述外部登陆的UserLoginInfo |
I use the FindAsync method defined by the user manager class to locate the user based on the value of the ExternalLoginInfo.Login property, which returns an AppUser object if the user has been authenticated with the application before:
我使用了由用户管理器类所定义的FindAsync方法,以便根据ExternalLoginInfo.Login属性的值对用户进行定位,若是用户以前在应用程序中已经认证,该属性会返回一个AppUser对象:
... AppUser user = await UserManager.FindAsync(loginInfo.Login); ...
If the FindAsync method doesn’t return an AppUser object, then I know that this is the first time that this user has logged into the application, so I create a new AppUser object, populate it with values, and save it to the database. I also save details of how the user logged in so that I can find them next time:
若是FindAsync方法返回的不是AppUser对象,那么我便知道这是用户首次登陆应用程序,因而便建立了一个新的AppUser对象,填充该对象的值,并将其保存到数据库。我还保存了用户如何登陆的细节,以便下次可以找到他们:
... result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login); ...
All that remains is to generate an identity the user, copy the claims provided by Google, and create an authentication cookie so that the application knows the user has been authenticated:
剩下的事情只是生成该用户的标识了,拷贝Google提供的声明(Claims),并建立一个认证Cookie,以使应用程序知道此用户已认证:
... ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(loginInfo.ExternalIdentity.Claims); AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident); ...
There is one further change that I need to make before I can test Google authentication: I need to change the account verification I set up in Chapter 13 because it prevents accounts from being created with e-mail addresses that are not within the example.com domain. Listing 15-25 shows how I removed the verification from the AppUserManager class.
在测试Google认证以前还须要一处修改:须要修改第13章所创建的帐号验证,由于它不容许example.com域以外的E-mail地址建立帐号。清单15-25显示了如何在AppUserManager类中删除这种验证。
Listing 15-25. Disabling Account Validation in the AppUserManager.cs File
清单15-25. 在AppUserManager.cs文件中取消帐号验证
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models;
namespace Users.Infrastructure { public class AppUserManager : UserManager<AppUser> {
public AppUserManager(IUserStore<AppUser> store) : base(store) { }
public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) {
AppIdentityDbContext db = context.Get<AppIdentityDbContext>(); AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
manager.PasswordValidator = new CustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true };
//manager.UserValidator = new CustomUserValidator(manager) { // AllowOnlyAlphanumericUserNames = true, // RequireUniqueEmail = true //};
return manager; } } }
Tip you can use validation for externally authenticated accounts, but I am just going to disable the feature for simplicity.
提示:也可使用外部已认证帐号的验证,但这里出于简化,取消了这一特性。
To test authentication, start the application, click the Log In via Google button, and provide the credentials for a valid Google account. When you have completed the authentication process, your browser will be redirected back to the application. If you navigate to the /Claims/Index URL, you will be able to see how claims from the Google system have been added to the user’s identity, as shown in Figure 15-7.
为了测试认证,启动应用程序,经过点击“Log In via Google(经过Google登陆)”按钮,并提供有效的Google帐号凭据。当你完成了认证过程时,浏览器将被重定向回应用程序。若是导航到/Claims/Index URL,便可以看到来自Google系统的声明(Claims),已被添加到用户的标识中了,如图15-7所示。
Figure 15-7. Claims from Google
图15-7. 来自Google的声明(Claims)
In this chapter, I showed you some of the advanced features that ASP.NET Identity supports. I demonstrated the use of custom user properties and how to use database migrations to preserve data when you upgrade the schema to support them. I explained how claims work and how they can be used to create more flexible ways of authorizing users. I finished the chapter by showing you how to authenticate users via Google, which builds on the ideas behind the use of claims.
本章向你演示了ASP.NET Identity所支持的一些高级特性。演示了自定义用户属性的使用,还演示了在升级数据架构时,如何使用数据库迁移保护数据。我解释了声明(Claims)的工做机制,以及如何将它们用于建立更灵活的用户受权方式。最后演示了如何经过Google进行认证结束了本章,这是创建在使用声明(Claims)的思想基础之上的。
看完此文若是以为有所收获,请给个推荐。 你的推荐是我继续下去的动力,也会让更多人关注并获益,这也是你的贡献。