咨詢電話:023-6276-4481
熱門文章
電 話:023-6276-4481
郵箱:broiling@qq.com
地址:重慶市南岸區(qū)亞太商谷6幢25-2
本章將完成對ASP.NET Identity的描述,向你展示它所提供的一些高級(jí)特性。我將演示,你可以擴(kuò)展ASP.NET Identity的數(shù)據(jù)庫架構(gòu),其辦法是在用戶類上定義一些自定義屬性。也會(huì)演示如何使用數(shù)據(jù)庫遷移,這樣可以運(yùn)用自定義屬性,而不必刪除ASP.NET Identity數(shù)據(jù)庫中的數(shù)據(jù)。還會(huì)解釋ASP.NET Identity如何支持聲明(Claims)概念,并演示如何將它們靈活地用來對動(dòng)作方法進(jìn)行授權(quán)訪問。最后向你展示ASP.NET Identity很容易通過第三方部件來認(rèn)證用戶,以此結(jié)束本章以及本書。將要演示的是使用Google賬號(hào)認(rèn)證,但ASP.NET Identity對于Microsoft、Facebook以及Twitter賬號(hào),都有內(nèi)建的支持。表15-1是本章概要。
問題 | 解決方案 | 清單號(hào) |
---|---|---|
存儲(chǔ)用戶的附加信息 | 定義自定義用戶屬性 | 1–3, 8–11 |
更新數(shù)據(jù)庫架構(gòu)而不刪除用戶數(shù)據(jù) | 執(zhí)行數(shù)據(jù)庫遷移 | 4–7 |
執(zhí)行細(xì)粒度授權(quán) | 使用聲明(Claims) | 12–14 |
添加用戶的聲明(Claims) | 使用ClaimsIdentity.AddClaims方法 | 15–19 |
基于聲明(Claims)值授權(quán)訪問 | 創(chuàng)建一個(gè)自定義的授權(quán)過濾器注解屬性 | 20–21 |
通過第三方認(rèn)證 | 安裝認(rèn)證提供器的NuGet包,將請求重定向到該提供器,并指定一個(gè)創(chuàng)建用戶賬號(hào)的回調(diào)URL。 | 22–25 |
本章打算繼續(xù)使用第13章創(chuàng)建并在第14章增強(qiáng)的Users項(xiàng)目。對應(yīng)用程序無需做什么改變,但需要啟動(dòng)應(yīng)用程序,并確保數(shù)據(jù)庫中有一些用戶。圖15-1顯示了數(shù)據(jù)庫的狀態(tài),它含有上一章的用戶Admin、Alice、Bob以及Joe。為了檢查用戶,請啟動(dòng)應(yīng)用程序,請求/Admin/Index URL,并以Admin用戶進(jìn)行認(rèn)證。
圖15-1. Identity數(shù)據(jù)庫中的最初用戶
本章還需要一些角色。我用RoleAdmin控制器創(chuàng)建了角色Users和Employees,并為這些角色指定了一些用戶,如表15-2所示。
角色 | 成員 |
---|---|
Users | Alice, Joe |
Employees | Alice, Bob |
圖15-2顯示了由RoleAdmin控制器所顯示出來的必要的角色配置。
圖15-2. 配置本章所需的角色
我在第13章創(chuàng)建AppUser類來表示用戶時(shí)曾做過說明,基類定義了一組描述用戶的基本屬性,如E-mail地址、電話號(hào)碼等。大多數(shù)應(yīng)用程序還需要存儲(chǔ)用戶的更多信息,包括持久化應(yīng)用程序愛好以及地址等細(xì)節(jié)——簡言之,需要存儲(chǔ)對運(yùn)行應(yīng)用程序有用并且在各次會(huì)話之間應(yīng)當(dāng)保持的任何數(shù)據(jù)。在ASP.NET Membership中,這是通過用戶資料(User Profile)系統(tǒng)來處理的,但ASP.NET Identity采取了一種不同的辦法。
因?yàn)锳SP.NET Identity默認(rèn)是使用Entity Framework來存儲(chǔ)其數(shù)據(jù)的,定義附加的用戶信息只不過是給用戶類添加屬性的事情,然后讓Code First特性去創(chuàng)建需要存儲(chǔ)它們的數(shù)據(jù)庫架構(gòu)即可。表15-3描述了自定義用戶屬性的情形。
問題 | 回答 |
---|---|
什么是自定義用戶屬性? | 自定義用戶屬性讓你能夠存儲(chǔ)附加的用戶信息,包括他們的愛好和設(shè)置。 |
為何要關(guān)心它? | 設(shè)置的持久化存儲(chǔ)意味著,用戶不必每次登錄到應(yīng)用程序時(shí)都提供同樣的信息。 |
在MVC框架中如何使用它? | 此特性不是由MVC框架直接使用的,但它在動(dòng)作方法中使用是有效的。 |
清單15-1演示了如何給AppUser類添加一個(gè)簡單的屬性,用以表示用戶生活的城市。
清單15-1. 在AppUser.cs文件中添加屬性
using System; using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models {}
這里定義了一個(gè)枚舉,名稱為Cities,它定義了一些大城市的值,另外給AppUser類添加了一個(gè)名稱為City的屬性。為了讓用戶能夠查看和編輯City屬性,給Home控制器添加了幾個(gè)動(dòng)作方法,如清單15-2所示。
清單15-2. 在HomeController.cs文件中添加對自定義屬性的支持
using System.Web.Mvc; using System.Collections.Generic; using System.Web; using System.Security.Principal; 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; } } }
我添加了一個(gè)CurrentUser屬性,它使用AppUserManager類接收了表示當(dāng)前用戶的AppUser實(shí)例。在GET版本的UserProps動(dòng)作方法中,傳遞了這個(gè)AppUser對象作為視圖模型。而在POST版的方法中用它更新了City屬性的值。清單15-3顯示了UserProps.cshtml視圖,它顯示了City屬性的值,并包含一個(gè)修改它的表單。
清單15-3. Views/Home文件夾中UserProps.cshtml文件的內(nèi)容
@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> }
警告:創(chuàng)建了視圖之后不要啟動(dòng)應(yīng)用程序。在以下小節(jié)中,將演示如何保留數(shù)據(jù)庫的內(nèi)容,如果現(xiàn)在啟動(dòng)應(yīng)用程序,將會(huì)刪除ASP.NET Identity的用戶。
Entity Framework Code First特性的默認(rèn)行為是,一旦修改了派生數(shù)據(jù)庫架構(gòu)的類,便會(huì)刪除數(shù)據(jù)庫中的數(shù)據(jù)表,并重新創(chuàng)建它們。在第14章可以看到這種情況,在我添加角色支持時(shí):當(dāng)重啟應(yīng)用程序后,數(shù)據(jù)庫被重置,用戶賬號(hào)也丟失。
不要啟動(dòng)應(yīng)用程序,但如果你這么做了,會(huì)看到類似的效果。在開發(fā)期間刪除數(shù)據(jù)沒什么問題,但如果在產(chǎn)品設(shè)置中這么做了,通常是災(zāi)難性的,因?yàn)樗鼤?huì)刪除所有真實(shí)的用戶賬號(hào),而備份恢復(fù)是很痛苦的事。在本小節(jié)中,我打算演示如何使用數(shù)據(jù)庫遷移特性,它能以比較溫和的方式更新Code First的架構(gòu),并保留架構(gòu)中的已有數(shù)據(jù)。
第一個(gè)步驟是在Visual Studio的“Package Manager Console(包管理器控制臺(tái))”中發(fā)布以下命令:
Enable-Migrations –EnableAutomaticMigrations
它啟用了數(shù)據(jù)庫的遷移支持,并在“Solution Explorer(解決方案資源管理器)”創(chuàng)建一個(gè)Migrations文件夾,其中含有一個(gè)Configuration.cs類文件,內(nèi)容如清單15-4所示。
清單15-4. Configuration.cs文件的內(nèi)容
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. // 此方法將在遷移到最新版本時(shí)調(diào)用 // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // 例如,你可以使用DbSet<T>.AddOrUpdate()輔助器方法來避免創(chuàng)建重復(fù)的種子數(shù)據(jù) // // context.People.AddOrUpdate( // p => p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // } } }
提示:你可能會(huì)覺得奇怪,為什么要在管理NuGet包的控制臺(tái)中輸入數(shù)據(jù)庫遷移的命令?答案是“Package Manager Console(包管理控制臺(tái))”是真正的PowerShell,這是Visual studio冒用的一個(gè)通用工具。你可以使用此控制臺(tái)發(fā)送大量的有用命令,詳見http://go.microsoft.com/fwlink/?LinkID=108518。
這個(gè)類將用于把數(shù)據(jù)庫中的現(xiàn)有內(nèi)容遷移到新的數(shù)據(jù)庫架構(gòu),Seed方法的調(diào)用為更新現(xiàn)有數(shù)據(jù)庫記錄提供了機(jī)會(huì)。在清單15-5中可以看到,我如何用Seed方法為新的City屬性設(shè)置默認(rèn)值,City是添加到AppUser類中自定義屬性。(為了體現(xiàn)我一貫的編碼風(fēng)格,我對這個(gè)類文件也進(jìn)行了更新。)
清單15-5. 在Configuration.cs文件中管理已有內(nèi)容
namespace Users.Migrations { internal sealed class Configuration : DbMigrationsConfiguration<AppIdentityDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Users.Infrastructure.AppIdentityDbContext"; } protected override void Seed(AppIdentityDbContext context) { } } }
你可能會(huì)注意到,添加到Seed方法中的許多代碼取自于IdentityDbInit類,在第14章中我用這個(gè)類將管理用戶植入了數(shù)據(jù)庫。這是因?yàn)檫@個(gè)新添加的、用以支持?jǐn)?shù)據(jù)庫遷移的Configuration類,將代替IdentityDbInit類的種植功能,我很快便會(huì)更新這個(gè)類。除了要確保有admin用戶之外,在Seed方法中的重要語句是那些為AppUser類的City屬性設(shè)置初值的語句,如下所示:
... foreach (AppUser dbUser in userMgr.Users) { dbUser.City = Cities.PARIS;} context.SaveChanges(); ...
你不一定要為新屬性設(shè)置默認(rèn)值——這里只是想演示Configuration類中的Seed方法,可以用它更新數(shù)據(jù)庫中的已有用戶記錄。
警告:在用于真實(shí)項(xiàng)目的Seed方法中為屬性設(shè)置值時(shí)要小心,因?yàn)槟忝恳淮涡薷募軜?gòu)時(shí),都會(huì)運(yùn)用這些值,這會(huì)將自執(zhí)行上一次架構(gòu)更新之后,用戶設(shè)置的任何數(shù)據(jù)覆蓋掉。這里設(shè)置City屬性的值只是為了演示它能夠這么做。
在Configuration類中添加種植代碼的原因是我需要修改IdentityDbInit類。此時(shí),IdentityDbInit類派生于描述性命名的DropCreateDatabaseIfModelChanges<AppIdentityDbContext> 類,和你相像的一樣,它會(huì)在Code First類改變時(shí)刪除整個(gè)數(shù)據(jù)庫。清單15-6顯示了我對IdentityDbInit類所做的修改,以防止它影響數(shù)據(jù)庫。
清單15-6. 在AppIdentityDbContext.cs文件是阻止數(shù)據(jù)庫架構(gòu)變化
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(); } } } }
我刪除了這個(gè)類中所定義的方法,并將它的基類改為NullDatabaseInitializer<AppIdentityDbContext> ,它可以防止架構(gòu)修改。
剩下的事情只是生成并運(yùn)用遷移了。首先,在“Package Manager Console(包管理器控制臺(tái))”中執(zhí)行以下命令:
Add-Migration CityProperty
這創(chuàng)建了一個(gè)名稱為CityProperty的新遷移(我比較喜歡讓遷移的名稱反映出我所做的修改)。這會(huì)在文件夾中添加一個(gè)新的類文件,而且其命名會(huì)反映出該命令執(zhí)行的時(shí)間以及遷移名稱,例如,我的這個(gè)文件名稱為201402262244036_CityProperty.cs。該文件的內(nèi)容含有遷移期間Entity Framework修改數(shù)據(jù)庫的細(xì)節(jié),如清單15-7所示。
清單15-7. 201402262244036_CityProperty.cs文件的內(nèi)容
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"); } } }
Up方法描述了在數(shù)據(jù)庫升級(jí)時(shí),需要對架構(gòu)所做的修改,在這個(gè)例子中,意味著要在AspNetUsers數(shù)據(jù)表中添加City數(shù)據(jù)列,該數(shù)據(jù)表是ASP.NET Identity數(shù)據(jù)庫用來存儲(chǔ)用戶記錄的。
最后一步是執(zhí)行遷移。無需啟動(dòng)應(yīng)用程序,只需在“Package Manager Console(包管理器控制臺(tái))”中運(yùn)行以下命令即可:
Update-Database –TargetMigration CityProperty
這會(huì)修改數(shù)據(jù)庫架構(gòu),并執(zhí)行Configuration.Seed方法中的代碼。已有用戶賬號(hào)會(huì)被保留,且增強(qiáng)了City屬性(我在Seed方法中已將其設(shè)置為“Paris”)。
為了測試遷移的效果,啟動(dòng)應(yīng)用程序,導(dǎo)航到/Home/UserProps URL,并以Identity中的用戶(例如Alice,口令MySecret)進(jìn)行認(rèn)證。一旦已被認(rèn)證,便會(huì)看到該用戶City屬性的當(dāng)前值,并可以對其進(jìn)行修改,如圖15-3所示。
圖15-3. 顯示和個(gè)性自定義用戶屬性
現(xiàn)在,已經(jīng)建立了數(shù)據(jù)庫遷移,我打算再定義一個(gè)屬性,這恰恰演示了如何處理持續(xù)不斷的修改,也為了演示Configuration.Seed方法更有用(至少無害)的示例。清單15-8顯示了我在AppUser類上添加了一個(gè)Country屬性。
清單15-8. 在AppUserModels.cs文件中添加另一個(gè)屬性
using System; using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models { public enum Cities { LONDON, PARIS, CHICAGO } } } }
我已經(jīng)添加了一個(gè)枚舉,它定義了國家名稱。還添加了一個(gè)輔助器方法,它可以根據(jù)City屬性選擇一個(gè)國家。清單15-9顯示了對Configuration類所做的修改,以使Seed方法根據(jù)City設(shè)置Country屬性,但只當(dāng)Country為NONE時(shí)才進(jìn)行設(shè)置(在遷移數(shù)據(jù)庫時(shí),所有用戶都是NONE,因?yàn)镋ntity Framework會(huì)將枚舉列設(shè)置為枚舉的第一個(gè)值)。
清單15-9. 在Configuration.cs文件中修改數(shù)據(jù)庫種子
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