ASP.NET Identity是微軟所貢獻的開源項目,用來提供ASP.NET的驗證、受權等等機制。在ASP.NET Identity裏除了提供最基礎的:用戶註冊、密碼重設、密碼驗證等等基礎功能以外,也提供了進階的:Cookie登入、Facebook登入、Google登入等等進階功能。套用這些功能模塊,讓開發人員能夠快速的在ASP.NET站臺上,提供驗證、受權等等機制。git
可是在企業中,開發人員經常會遇到一種開發情景就是:企業裏已經有一套既有身分系統,這個系統提供了:用戶註冊、密碼重設、密碼驗證等等功能,新開發的ASP.NET站臺,必需要串接既有身分系統來提供驗證、受權等等機制。這個既有身分系統,多是大賣場會員管理系統、也多是銀行賬戶管理系統,它們的註冊審覈機制有一套嚴謹而且固定的流程。github
在這樣的開發情景中,開發人員可能會選擇透過程序接口、OAuth機制等等方式,將既有身分系統整合成爲ASP.NET Identity的驗證提供者。這樣兩個系統之間的整合,除了有必定的高技術門坎以外。在整合以後,兩個系統之間互相重迭的功能模塊,操做流程的衝突該如何處理,也是一個須要額外考慮的複雜問題。數據庫
一個好消息是,ASP.NET Identity擁有高度模塊化的軟件架構。在ASP.NET Identity中,將Cookie登入、Facebook登入、Google登入等等功能模塊,切割爲獨立的ASP.NET Security套件。開發人員徹底能夠直接套用ASP.NET Security套件,快速整合既有的身分系統,就能夠提供ASP.NET站臺所需的驗證、受權等等機制。本篇文章介紹如何套用ASP.NET Security來整合既有身分系統,用以提供ASP.NET站臺所需的驗證、受權等等機制。主要爲本身留個紀錄,也但願能幫助到有須要的開發人員。服務器
範例程序代碼:下載地址app
開始套用ASP.NET Security以前,先創建一個空白的MVC項目,來提供一個新的ASP.NET站臺。而且變動預設的Web服務器URL爲:「http://localhost:41532/」,方便完成後續的開發步驟。async
再來在MVC項目里加入三個ASP.NET Security的NuGet套件參考:Microsoft.AspNet.Authentication、Microsoft.AspNet.Authentication.Cookies、Microsoft.AspNet.Authentication.Facebook。ide
接着創建AccountController以及相關的View,用以提供登入頁面,讓使用者能夠選擇使用哪一種模式登入系統。模塊化
public class AccountController : Controller { // Methods public IActionResult Login(string returnUrl = null) { // ViewData this.ViewData["ReturnUrl"] = returnUrl; // Return return View(); } }
再來在MVC項目內加入下列程序代碼,用以掛載與設定後續要使用的兩個CookieAuthenticationMiddleware。(關於程序代碼的相關背景知識,請參閱技術剖析說明:ASP.NET Identity登入技術剖析)post
public class Startup { public void ConfigureServices(IServiceCollection services) { // Authentication services.AddAuthentication(options => { options.SignInScheme = IdentityOptions.Current.ExternalCookieAuthenticationScheme; }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // Authentication app.UseCookieAuthentication(options => { options.AuthenticationScheme = IdentityOptions.Current.ApplicationCookieAuthenticationScheme; options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.LoginPath = new PathString("/Account/login"); }); app.UseCookieAuthentication(options => { options.AuthenticationScheme = IdentityOptions.Current.ExternalCookieAuthenticationScheme; options.AutomaticAuthenticate = false; options.AutomaticChallenge = false; options.LoginPath = null; }); } }
最後在MVC項目內,創建ExistingIdentitySystem這個類別用來仿真既有身分系統。爲了方便理解系統,ExistingIdentitySystem裏的PasswordSignIn(密碼登入)、ExternalSignIn(第三方登入ex:FB登入)等方法都直接回傳成功訊息,而GetUserById(取得使用者)這個方法則是直接回傳固定的用戶信息。(在正式環境開發時,上述方法能夠實做爲透過WebAPI、或是直接連通數據庫等等方式,與既有身分系統取得相關信息。)
public class ExistingIdentitySystem { // Methods public ExistingUser GetUserById(string userId) { // Result var user = new ExistingUser(); user.Id = "Clark.Lab@hotmail.com"; user.Name = "Clark"; user.Birthday = DateTime.Now; // Return return user; } public bool PasswordSignIn(string userId, string password) { // Return return true; } public bool ExternalSignIn(string userId, string externalProvider) { switch (externalProvider) { case "Facebook": return true; default: return true; } } } public class ExistingUser { // Properties public string Id { get; set; } public string Name { get; set; } public DateTime Birthday { get; set; } }
完成上述步驟後,接着着手開發Facebook驗證。首先開發人員能夠到Facebook開發者中心(https://developers.facebook.com/),註冊一個新的APP帳號。(測試用的Site URL爲先前步驟定義的:「http://localhost:41532/」)
接着在MVC項目內加入下列程序代碼,用以掛載與設定FacebookAuthenticationMiddleware。在這其中AppId、AppSecret是Facebook開發者中心提供的APP帳號數據,而Scope、UserInformationEndpoint兩個參數則是定義要額外取得用戶的E-Mail信息。
public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // Authentication app.UseFacebookAuthentication(options => { options.AppId = "770764239696406"; options.AppSecret = "2eecc0b9ef785e43bcd4779e2803ba0f"; options.Scope.Add("email"); options.UserInformationEndpoint = "https://graph.facebook.com/v2.5/me?fields=id,name,email"; }); } }
再來打開AccountController加入下列程序代碼以及對應的View,用以提供ASP.NET站臺處理Facebook這類的第三方登入(ExternalLogin)。在這其中,ExternalLogin用來發起一個驗證挑戰(Challenge),系統會依照externalProvider參數,來決定是要向Facebook或是其餘第三方系統作驗證。
當用戶經過驗證後,系統會調用ExternalLoginCallback來處理驗證結果。在ExternalLoginCallback裏會取得驗證結果中FBUser的UserId,用來與ExistingIdentitySystem作驗證。若是驗證經過,會接着從ExistingIdentitySystem取得對應的ExistingUser、再轉換爲APPUser來真正登入系統。(關於程序代碼的相關背景知識,請參閱技術剖析說明:ASP.NET Identity登入技術剖析)
public class AccountController : Controller { public IActionResult ExternalLogin(string externalProvider, string returnUrl = null) { // AuthenticationProperties var authenticationProperties = new AuthenticationProperties(); authenticationProperties.Items.Add("ExternalProvider", externalProvider); authenticationProperties.RedirectUri = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); // Return return new ChallengeResult(externalProvider, authenticationProperties); } public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null) { // AuthenticateContext var authenticateContext = new AuthenticateContext(IdentityOptions.Current.ExternalCookieAuthenticationScheme); await this.HttpContext.Authentication.AuthenticateAsync(authenticateContext); // AuthenticateInfo string userId = authenticateContext.Principal.FindFirst(ClaimTypes.Email).Value; string externalProvider = authenticateContext.Properties["ExternalProvider"] as string; // Login var existingIdentitySystem = new ExistingIdentitySystem(); if (existingIdentitySystem.ExternalSignIn(userId, externalProvider) == false) { throw new InvalidOperationException(); } // ExistingUser var existingUser = existingIdentitySystem.GetUserById(userId); if (existingUser == null) throw new InvalidOperationException(); // ApplicationUser var applicationIdentity = new ClaimsIdentity(IdentityOptions.Current.ApplicationCookieAuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role); applicationIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, existingUser.Id)); applicationIdentity.AddClaim(new Claim(ClaimTypes.Name, existingUser.Name)); var applicationUser = new ClaimsPrincipal(applicationIdentity); // Cookie await this.HttpContext.Authentication.SignInAsync(IdentityOptions.Current.ApplicationCookieAuthenticationScheme, applicationUser); await this.HttpContext.Authentication.SignOutAsync(IdentityOptions.Current.ExternalCookieAuthenticationScheme); // Return return Redirect(returnUrl); } }
完成上述步驟後,接着着手開發Password驗證。打開AccountController加入下列程序代碼以及對應的View,用以提供ASP.NET站臺處理Password驗證。在這其中,PasswordLogin會接收用戶輸入的帳號密碼,用來與ExistingIdentitySystem作驗證。若是驗證經過,會接着從ExistingIdentitySystem取得ExistingUser、再轉換爲APPUser來真正登入系統。(關於程序代碼的相關背景知識,請參閱技術剖析說明:ASP.NET Identity登入技術剖析)
public class AccountController : Controller { public async Task<IActionResult> PasswordLogin(string userId, string password, string returnUrl = null) { // Login var existingIdentitySystem = new ExistingIdentitySystem(); if (existingIdentitySystem.PasswordSignIn(userId, password) == false) { throw new InvalidOperationException(); } // ExistingUser var existingUser = existingIdentitySystem.GetUserById(userId); if (existingUser == null) throw new InvalidOperationException(); // ApplicationUser var applicationIdentity = new ClaimsIdentity(IdentityOptions.Current.ApplicationCookieAuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role); applicationIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, existingUser.Id)); applicationIdentity.AddClaim(new Claim(ClaimTypes.Name, existingUser.Name)); var applicationUser = new ClaimsPrincipal(applicationIdentity); // Cookie await this.HttpContext.Authentication.SignInAsync(IdentityOptions.Current.ApplicationCookieAuthenticationScheme, applicationUser); await this.HttpContext.Authentication.SignOutAsync(IdentityOptions.Current.ExternalCookieAuthenticationScheme); // Return return Redirect(returnUrl); } }
完成開發步驟後,當系統執行到打上[Authorize]標籤的Controller或是Action時,就會跳轉到Login頁面。
public class HomeController : Controller { [Authorize] public IActionResult Contact() { ViewData["Message"] = "Hello " + User.Identity.Name + "!"; return View(); } }
在Login頁面,當使用者選擇使用Facebook驗證,系統會跳轉到Facebook頁面進行驗證與受權。完成驗證受權的相關步驟後,使用者就能夠進入被打上[Authorize]標籤的Controller或是Action。
在Login頁面,當使用者選擇使用Password驗證,系統會使用Login頁面上輸入的帳號密碼來進行驗證與受權。完成驗證受權的相關步驟後,使用者就能夠進入被打上[Authorize]標籤的Controller或是Action。
範例程序代碼:下載地址