【ASP.NET Identity系列教程(三)】Identity高級技術

注:本文是【ASP.NET Identity系列教程】的第三篇。本系列教程詳細、完整、深刻地介紹了微軟的ASP.NET Identity技術,描述瞭如何運用ASP.NET Identity實現應用程序的用戶管理,以及實現應用程序的認證與受權等相關技術,譯者但願本系列教程能成爲掌握ASP.NET Identity技術的一份完整而有價值的資料。讀者如果可以按照文章的描述,一邊閱讀、一邊實踐、一邊理解,定能有意想不到的巨大收穫!但願本系列博文可以獲得廣大園友的高度推薦html





15 Advanced ASP.NET Identity
15 ASP.NET Identity高級技術

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

Table 15-1. Chapter Summary
表15-1. 本章概要
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

15.1 Preparing the Example Project
15.1 準備示例項目

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顯示了數據庫的狀態,它含有上一章的用戶AdminAliceBob以及Joe。爲了檢查用戶,請啓動應用程序,請求/Admin/Index URL,並以Admin用戶進行認證。web

圖15-1

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控制器建立了角色UsersEmployees,併爲這些角色指定了一些用戶,如表15-2所示。express

Table 15-2. The Types of Web Forms Code Nuggets
表15-2. 角色及成員(做者將此表的標題寫錯了——譯者注)
Role
角色
Members
成員
Users Alice, Joe
Employees Alice, Bob

Figure 15-2 shows the required role configuration displayed by the RoleAdmin controller.
圖15-2顯示了由RoleAdmin控制器所顯示出來的必要的角色配置。瀏覽器

圖15-2

Figure 15-2. Configuring the roles required for this chapter
圖15-2. 配置本章所需的角色cookie

15.2 Adding Custom User Properties
15.2 添加自定義用戶屬性

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描述了自定義用戶屬性的情形。架構

Table 15-3. Putting Cusotm User Properties in Context
表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框架直接使用的,但它在動做方法中使用是有效的。

15.2.1 Defining Custom Properties
15.2.1 定義自定義屬性

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的用戶。

15.2.2 Preparing for Database Migration
15.2.2 準備數據庫遷移

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屬性的值只是爲了演示它可以這麼作。

Changing the Database Context Class
修改數據庫上下文類

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> ,它能夠防止架構修改。

15.2.3 Performing the Migration
15.2.3 執行遷移

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」)。

15.2.4 Testing the Migration
15.2.4 測試遷移

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所示。

圖15-3

Figure 15-3. Displaying and changing a custom user property
圖15-3. 顯示和個性自定義用戶屬性

15.2.5 Defining an Additional Property
15.2.5 定義附加屬性

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屬性,但只當CountryNONE時才進行設置(在遷移數據庫時,全部用戶都是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屬性的值——後繼的遷移不會受到影響,所以不會失去用戶的選擇。

1. Adding Application Support
1. 添加應用程序支持

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... // ...出於簡化,這裏忽略了一些屬性... } }

2. Performing the Migration
2. 準備遷移

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所示。

圖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參數進行修改,會引發數據丟失,例如刪除數據表列。

15.3 Working with Claims
15.3 使用聲明(Claims)

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)。請讀者注意該單詞的單複數形式——譯者注

Table 15-4. Putting Claims in Context
表15-4. 聲明(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)。

15.3.1 Understanding Claims
15.3.1 理解聲明(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顯示了它所定義的與本章有關的成員。

Table 15-5. The Members Defined by the ClaimsIdentity Class
表15-5. ClaimsIdentity類所定義的成員
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所示。

Table 15-6. The Properties Defined by the Claim Class
表15-6. Claim類定義的屬性
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顯示了生成的內容。

圖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中重列了其內容。

Table 15-7. The Data Shown in Figure 15-5
表15-7. 圖15-5中顯示的數據
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聲明是AliceNameIdentifier聲明是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被賦予了UsersEmployees兩個角色這一事實。還有一個是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)了。

15.3.2 Creating and Using Claims
15.3.2 建立和使用聲明(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)。你可能須要退出,而後再次登陸纔會看到發生的變化。

圖15-6

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數據庫獲取的地點數據可能要比外部郵件列表提供器獲取的數據更爲精確和可信。

1. Applying Claims
1. 運用聲明(Claims)

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)有無限可能!要是沒有我這裏的提示,是否全部讀者在此處都會有所體會?——譯者注

15.3.3 Authorizing Access Using Claims
15.3.3 使用聲明(Claims)受權訪問

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實例,並且該用戶有一個帶有issuertype以及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方法。

15.4 Using Third-Party Authentication
15.4 使用第三方認證

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描述了事情的情形。

Table 15-8. Putting Third-Party Authentication in Context
表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。

15.4.1 Enabling Google Authentication
15.4.1 啓用Google認證

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所示。

Table 15-9. The NuGet Authenticaton Packages
表15-9. NuGet認證包
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所示:

Table 15-10. The Properties Defined by the ExternalLoginInfo Class
表15-10. ExternalLoginInfo類所定義的屬性
Name
名稱
Description
描述
DefaultUserName Returns the username
返回用戶名
Email 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);
...

15.4.2 Testing Google Authentication
15.4.2 測試Google認證

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所示。

圖15-7

Figure 15-7. Claims from Google
圖15-7. 來自Google的聲明(Claims)

15.5 Summary
15.5 小結

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)的思想基礎之上的。


看完此文若是以爲有所收穫,請給個推薦。 你的推薦是我繼續下去的動力,也會讓更多人關注並獲益,這也是你的貢獻。

相關文章
相關標籤/搜索