Asp.Net Identity學習筆記+MVC5默認項目解析

分類:IT技術 時間:2016-10-13

前言
ASP.NET Identity特性
Identity包
基本
IdentityUser
UserManager
準備工作
ApplicationDbContext
ApplicationUserManager
註冊案例
登入案例
用戶信息驗證
用戶名密碼驗證器
自定義驗證器

前言

本文簡單介紹Identity的使用,使用的案例是基於默認的Mvc5項目(只勾選MVC,不勾選WebApi),這個項目幾乎都與Identity有關.讀者可以拿著項目源碼對照地看.

本人弱雞一只,如果有哪些地方沒講述清楚請指出,如果是缺少某些背景知識抱歉本文不解釋.

ASP.NET Identity特性

  • One ASP.NET Identity 系統
  • 更容易加入用戶的個人資料信息
  • 持久化控制
  • 單元測試能力
  • 角色提供程序
  • 基於聲明的 (Claims Based)
  • 社交賬號登錄提供程序 (Social Login Providers)
  • Windows Azure Active Directory
  • OWIN 集成
  • NuGet 包

Identity包

Identity是依賴於EF的Code First 和Owin的,當然你可以自己拿著Micsoft.AspNet.Identity.Core重寫一份不依賴EF的Identity.

用戶數據庫由EF Code First創建,賬號等功能通過Owin的中間件形式加入到程序中(Owin中間件相當於Asp.Net 的Module)

  • Microsoft.AspNet.Identity.EntityFramework

    這個包容納了 ASP.NET Identity 基於 Entity Framework 的實現。它將 ASP.NET Identity 的數據和架構存入 SQL Server。

  • Microsoft.AspNet.Identity.Core

    這個包容納了 ASP.NET Identity 的核心接口。它可以用來編寫 ASP.NET Identity 的其他實現,用以支持其他持久化存儲系統,如 Windows Azure 表存儲, NoSQL 數據庫等等。

  • Microsoft.AspNet.Identity.OWIN

    這個包為 ASP.NET 應用程序提供了將 ASP.NET Identity 引入到 OWIN 身份驗證的功能。當你在為應用程序加入登錄功能,調用 OWIN cookie 身份驗證中間件來生成 cookie 時,會用到這個包。

如上圖所示Identity依賴了很多東西每個都是大框架,因此本文要求讀者有一定的EF Code First和Owin知識

基本

Identity采用EF Code First,他內置了一些類用戶創建數據庫,因此
在默認情況下Identity會創建下列表格

Identity用的數據庫上下文有點特別,是繼承IdentityDbContext,正是繼承了這個特殊的上下文,才會有那麽多默認表

  1. public class MyIdentityDbContext : IdentityDbContext<MyIdentityUser>
  2. {
  3. //可以在這裏擴展自己的表,配置數據表
  4. }

MyIdentityUser我自定義的,是實現IdentityUser接口的類

默認情況下是沒有數據庫的,直到創建一個新用戶,EF才會去創建數據庫
這個數據庫會創建在App_Data下

因為在Web.config配置了數據庫生成位置

  1. <connectionStrings>
  2. <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-DefaultMVC5-20160806094030.mdf;Initial Catalog=aspnet-DefaultMVC5-20160806094030;Integrated Security=True"
  3. providerName="system.Data.SqlClient" />
  4. </connectionStrings>

IdentityUser

對應數據表中AspNetUsers
該類描述用戶數據.我們先只註意用戶部分忽略登入記錄,角色,申明的部分

IdentityUser默認成員

名稱 描述 Claims 返回用戶的聲明集合 Email 返回用戶的E-mail地址 Id 返回用戶的唯一ID Logins 返回用戶的登錄集合 PasswordHash 返回哈希格式的用戶口令,在“實現Edit特性”中會用到它 Roles 返回用戶所屬的角色集合 PhoneNumber 返回用戶的電話號碼 SecurityStamp 返回變更用戶標識時被修改的值,例如被口令修改的值 UserName 返回用戶名

AspNetUser表結構

可以使用EF Code First相關的知識對默認表進行配置

  1. //改表名
  2. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  3. {
  4. modelBuilder.Entity<IdentityUser>().ToTable("MyUser");
  5. }
  6. //忽略列,註意不是所有列都能忽略
  7. modelBuilder.Entity<MyIdentityUser>().Ignore(x => x.PhoneNumberConfirmed);

UserManager

用戶管理類,其職責相當於業務邏輯層

名稱 描述 ChangePasswordAsync(id, old, new) 為指定用戶修改口令 CreateAsync(user) 創建一個不帶口令的新用戶 CreateAsync(user, pass) 創建一個帶有指定口令的新用戶 DeleteAsync(user) 刪除指定用戶 FindAsync(user, pass) 查找代表該用戶的對象,並認證其口令 FindByIdAsync(id) 查找與指定ID相關聯的用戶對象 FindByNameAsync(name) 查找與指定名稱相關聯的用戶對象,第14章“種植數據庫”時會用到這個方法 updateAsync(user) 將用戶對象的修改送入數據庫 Users 返回用戶枚舉

同樣的可以用繼承的方式擴展自己的用戶管理類

準備工作

在使用Identity前先要做一些配置
首先是Owin
默認的項目會創建一個Startup.cs,該類上的OwinStartupAttribute特性標註了該類為啟動類
這個類含有一個名稱為Configuration的方法,該方法由OWIN基礎架構進行調用,並為該方法傳遞一個Owin.IAppBuilder接口的實現,由它支持應用程序所需中間件的設置

  1. [assembly: OwinStartupAttribute(typeof(DefaultMVC5.Startup))]
  2. namespace DefaultMVC5
  3. {
  4. public partial class Startup
  5. {
  6. public void Configuration(IAppBuilder app)
  7. {
  8. ConfigureAuth(app);
  9. }
  10. }
  11. }

同時這個類是個部分類,在App_start中能找到另一部分ConfigureAuth就是用於配置Identity

  1. public void ConfigureAuth(IAppBuilder app)
  2. {
  3. app.CreatePerOwinContext(ApplicationDbContext.Create);
  4. app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
  5. //省略,先不解釋
  6. }

Startup只在網站啟動的時候執行,上面這段代碼的CreatePerOwinContext需要傳入一個委托,這個委托能返回一個對象,而這個對象在一次請求中是唯一的.所以非常時候放置類似數據庫上下文之類的類.
本質是每當用戶請求時候Owin講調用這些委托來創建對象,並把對象保存到OwinContext中.然後可以在應用程序的任何位置使用

  1. HttpContext.GetOwinContext().Get<ApplicationSignInManager>()
  2. //你可能會看到
  3. HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
  4. //這個泛型擴展方法內部調用了context.Get<TManager>();,感覺這個擴展方法只是用來打醬油的

來獲得這個對象.

GetOwinContext是擴展方法,他會從HttpContext.Items中獲得Owin之前保存在裏面的信息,再生成OwinContext

總之使用CreatePerOwinContext可以保存一個對象在Owin上下文,使得一次請求中用到同一個對象.

ApplicationDbContext

/Models/IdentityModels.cs

數據庫上下文類和用戶類都是繼承Identity內置類的,為了能擴展自己想要的表或表的字段

  1. public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
  2. {
  3. public ApplicationDbContext()
  4. : base("DefaultConnection", throwIfV1Schema: false)
  5. {
  6. }
  7. //給Owin用的
  8. public static ApplicationDbContext Create()
  9. {
  10. return new ApplicationDbContext();
  11. }
  12. }

ApplicationUserManager

/App_Start/IdentityConfig.cs

  1. public class ApplicationUserManager : UserManager<ApplicationUser>
  2. {
  3. public ApplicationUserManager(IUserStore<ApplicationUser> store)
  4. : base(store)
  5. {
  6. }
  7. public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
  8. {
  9. var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
  10. /*
  11. manager配置代碼
  12. */
  13. return manager;
  14. }
  15. }

值得註意的是Manager的創建需要用到UserStore,如果ApplicationUserManager相等於業務層,那麽他的職責相當於數據層.
還有一點是這個Create方法的參數與ApplicationDbContext的Create不同

  1. IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context

這個Create也也是能被Owin的CreatePerOwinContext使用.參數context就是Owin上下文,Create中使用context.Get<ApplicationDbContext>獲得保存在context中的ApplicationDbContext對象而不用再次手動創建

註冊案例

Controllers/AccountController.cs

賬號管理的相關代碼在這個控制器中,你會常看到這類代碼,從Owin上下文獲得ApplicationUserManager對象,以便管理用戶

  1. private ApplicationUserManager _userManager;
  2. public ApplicationUserManager UserManager
  3. {
  4. get
  5. {
  6. return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
  7. }
  8. private set
  9. {
  10. _userManager = value;
  11. }
  12. }
  1. [HttpPost]
  2. [AllowAnonymous]
  3. [ValidateAntiForgeryToken]
  4. public async Task<ActionResult> Register(RegisterViewModel model)
  5. {
  6. if (ModelState.IsValid)
  7. {
  8. //創建新用戶
  9. var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
  10. var result = await UserManager.CreateAsync(user, model.Password);
  11. if (result.Succeeded)
  12. {
  13. //如果註冊成功同時登入,SignInManager後面解釋
  14. await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
  15. return RedirectToAction("Index", "Home");
  16. }
  17. AddErrors(result);
  18. }
  19. // 如果我們進行到這一步時某個地方出錯,則重新顯示表單
  20. return View(model);
  21. }

登入案例

用戶登入後有一個比較重要的步驟讓網站記住這個用戶登入了(可以說是授權),傳統做法會把用戶數據類保存到Session中用戶再請求使用查看他是否在Session保存了用戶數據.而Session這種做法是利用Cookie來標識用戶.
在Identity中並不是用Session,但還是借用了Cookie
為了開啟Cookie授權在Startup類中使用這個中間件(Middleware)

  1. app.UseCookieAuthentication(new CookieAuthenticationOptions
  2. {
  3. AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
  4. LoginPath = new PathString("/Account/Login"),//當訪問未授權頁面時將自定跳轉到這個位置
  5. CookieName = "MyCookieName",//自定義Cookie名稱
  6. Provider = new CookieAuthenticationProvider
  7. {
  8. // 當用戶登錄時使應用程序可以驗證安全戳。
  9. // 這是一項安全功能,當你更改密碼或者向帳戶添加外部登錄名時,將使用此功能。
  10. OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
  11. validateInterval: TimeSpan.FromMinutes(30),
  12. regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
  13. }
  14. });

在亮出MVC5默認項目代碼前,我想先展示下<<Pro Asp.Net MVC5 Platform>>的代碼,因為他更加的直觀.

  1. [HttpPost]
  2. [AllowAnonymous]
  3. [ValidateAntiForgeryToken]
  4. public async Task<ActionResult> Login(LoginModel details, string returnUrl) {
  5. if (ModelState.IsValid) {
  6. AppUser user = await UserManager.FindAsync(details.Name,details.Password);
  7. if (user == null) {
  8. ModelState.AddModelError("", "Invalid name or password.");
  9. } else {
  10. //獲得用戶的標識,所有的標識都實現IIdentity接口,這個是基於聲明的標識,聲明下文再講,只要知道他與授權有關
  11. ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);
  12. AuthManager.SignOut();
  13. AuthManager.SignIn(new AuthenticationProperties {
  14. IsPersistent = false}, ident);
  15. return Redirect(returnUrl);
  16. }
  17. }
  18. ViewBag.returnUrl = returnUrl;
  19. return View(details);
  20. }

這塊代碼很直觀,獲得用戶賬號密碼,去數據庫查是否存在,如果存在就登入,順帶獲得用戶的聲明信息.

下面是MVC5默認項目中的代碼

  1. [HttpPost]
  2. [AllowAnonymous]
  3. [ValidateAntiForgeryToken]
  4. public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
  5. {
  6. if (!ModelState.IsValid)
  7. {
  8. return View(model);
  9. }
  10. // 這不會計入到為執行帳戶鎖定而統計的登錄失敗次數中
  11. // 若要在多次輸入錯誤密碼的情況下觸發帳戶鎖定,請更改為 shouldLockout: true
  12. var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
  13. switch (result)
  14. {
  15. case SignInStatus.Success:
  16. return RedirectToLocal(returnUrl);
  17. case SignInStatus.LockedOut:
  18. return View("Lockout");
  19. case SignInStatus.RequiresVerification:
  20. return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
  21. case SignInStatus.Failure:
  22. default:
  23. ModelState.AddModelError("", "無效的登錄嘗試。");
  24. return View(model);
  25. }
  26. }

這份代碼中並沒有上面那樣直觀,它用了SignInManager,這個是個ApplicationSignInManager類,很容易猜到他是自定義的類,繼承自SignInManager(Identity內置的).該類是利用UserManager執行一系列登入操作
其實內部實現大致就與上上段代碼一樣,也是查找用戶判斷是否存在….

但它做的更多,單PasswordSignInAsync這個方法它不僅負責查詢用戶,登入用戶,還負責記錄用戶登入記錄(登入失敗幾次,對於被鎖定用戶的處理…).

用戶信息驗證

任何用戶輸入都需要驗證,用戶信息更是如此.
在默認項目中不僅利用了MVC內置的模型驗證,還利用了Identity的驗證器.
就拿註冊來說,首先自定義了ViewModel,並打上驗證特性

  1. public class RegisterViewModel
  2. {
  3. [Required]
  4. [EmailAddress]
  5. [Display(Name = "電子郵件")]
  6. public string Email { get; set; }
  7. [Required]
  8. [StringLength(100, Errormessage = "{0} 必須至少包含 {2} 個字符。", MinimumLength = 6)]
  9. [DataType(DataType.Password)]
  10. [Display(Name = "密碼")]
  11. public string Password { get; set; }
  12. [DataType(DataType.Password)]
  13. [Display(Name = "確認密碼")]
  14. [Compare("Password", ErrorMessage = "密碼和確認密碼不匹配。")]
  15. public string ConfirmPassword { get; set; }
  16. }

這裏的驗證能配合HtmlHelper實現客戶端校驗.
其次利用Identity的驗證器,關鍵點在下面代碼第一行,嘗試登入,如果失敗的話把result中的錯誤信息返回給前端,AddErrors方法添加的是模型級錯誤,通過@Html.ValidationSummary()能顯示出來

  1. var result = await UserManager.CreateAsync(user, model.Password);
  2. if (result.Succeeded)
  3. {
  4. await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
  5. return RedirectToAction("Index", "Home");
  6. }
  7. AddErrors(result);
  8. ......
  9. private void AddErrors(IdentityResult result)
  10. {
  11. foreach (var error in result.Errors)
  12. {
  13. ModelState.AddModelError("", error);
  14. }
  15. }

用戶名密碼驗證器

App_Start/ApplicationUserManager/Create

  1. // 配置用戶名的驗證邏輯
  2. manager.UserValidator = new UserValidator<ApplicationUser>(manager)
  3. {
  4. AllowOnlyAlphanumericUserNames = false,
  5. RequireUniqueEmail = true
  6. };
  7. // 配置密碼的驗證邏輯
  8. manager.PasswordValidator = new PasswordValidator
  9. {
  10. RequiredLength = 6,
  11. RequireNonLetterOrDigit = true,
  12. RequireDigit = true,
  13. RequireLowercase = true,
  14. RequireUppercase = true,
  15. };

PasswordValidator屬性定義

名稱 描述 RequiredLength 指定合法口令的最小長度 RequireNonLetterOrDigit 當設置為true時,合法口令必須含有非字母和數字的字符 RequireDigit 當設置為true時,合法口令必須含有數字 RequireLowercase 當設置為true時,合法口令必須含有小寫字母 RequireUppercase 當設置為true時,合法口令必須含有大寫字母

UserValidator屬性定義

名稱 描述 AllowOnlyAlphanumericUserNames 當為true時,用戶名只能含有字母數字字符 RequireUniqueEmail 當為true時,郵件地址必須唯一

配置驗證器後就能在有UserManager的地方使用它UserManager.PasswordValidator.ValidateAsync.
通常SignInAsync這些方法內部都會調用他們的.

自定義驗證器

自定義用戶驗證器

  1. public class CustomUserValidator : UserValidator<AppUser> {
  2. public CustomUserValidator(AppUserManager mgr) : base(mgr) {
  3. }
  4. public override async Task<IdentityResult> ValidateAsync(AppUser user) {
  5. //使用內建驗證策略
  6. IdentityResult result = await base.ValidateAsync(user);
  7. //在此基礎上添加自己的驗證策略
  8. if (!user.Email.ToLower().EndsWith("@example.com")) {
  9. var errors = result.Errors.ToList();
  10. errors.Add("Only example.com email addresses are allowed");
  11. result = new IdentityResult(errors);
  12. }
  13. return result;
  14. }
  15. }

自定義口令驗證器

  1. public class CustomPasswordValidator : PasswordValidator {
  2. public override async Task<IdentityResult> ValidateAsync(string pass) {
  3. IdentityResult result = await base.ValidateAsync(pass);
  4. if (pass.Contains("12345")) {
  5. var errors = result.Errors.ToList();
  6. errors.Add("Passwords cannot contain numeric sequences");
  7. result = new IdentityResult(errors);
  8. }
  9. return result;
  10. }
  11. }


來自為知筆記(Wiz)


Tags: Windows 用戶名 數據庫 中間件 項目

文章來源:


ads
ads

相關文章
ads

相關文章

ad