注:本文系作者原創,但可隨意轉載。若有任何疑問或錯誤,歡迎與原作者交流,原文地址:http://www.cnblogs.com/lyosaki88/p/aspnet-itentity-ii-emailconfirmed.html
==========================================================================================================
今天抽空更新了SAMPLE CODE。我把代碼托管到了CODEPLEX平台。歡迎下載。 https://identityiiemailconfirmation.codeplex.com/
==========================================================================================================
一個星期前,也就是3月20日,微軟發布了Asp.Net Identity 2.0 RTM。功能更加強大,也更加穩定。Identity這個東西現在版本還比較低,每次發布新版本都會有較多改動。
2.0新增了很多功能,比如
- “雙重認證(TFA)" --就是類似密保登陸的功能
- ”賬號鎖定”--可以設置賬號在短時間內登陸失敗達到一定次數則在幾分鍾內被禁止登陸
- “賬號認證”--即現在普遍的登陸模式,用戶名即郵箱,注冊后需要認證才可以登陸
- “密碼找回”--這個常用功能以前一直未被集成到Identity中
- “單點登出(SSO)”--也就是同時打開了幾個頁面,在任何頁面退出,都會導致其他頁面的TOKEN失效,從而不能進行賬戶下的操作
- “主鍵變更”--在1.0版本中用戶表的主鍵是string類型的用戶名,現在可以設置任意類型的主鍵,如int,Guid 等
- “集合查詢”-- 支持以集合的方式查詢Users和Roles表
- “刪除賬號”--現在這個功能可以使用UserManager管理類來實現,過去只能通過dbContext直接操作數據表是很麻煩的
- “增強密碼規則”--在注冊時,可以設置密碼規則,包括位數,是否必須大寫字母,是否必須小寫字母,是否必須數字,是否必須特殊符號等,規則更加強大
其他更多功能請參考官方博客http://blogs.msdn.com/b/webdev/archive/2014/03/20/test-announcing-rtm-of-asp-net-identity-2-0-0.aspx
========================================================================================
當然,上面的功能具體如何實現還需要我們來編寫具體的代碼,比如實現第三方登陸等,Identity只是給出了基本的腳手架。
下面我基於Identity 2.0新的腳手架來實現他的 注冊賬號認證 功能,即使用郵箱注冊后,系統發送郵件至用戶郵箱,用戶打開郵箱點擊超鏈接激活賬號后才可以登陸。
首先,拿到新東西當然是看文檔,然后下載Sample看下基本功能是如何實現的。
官方給出的Sample的安裝方法,使用VS 打開菜單 “工具”--》“NuGet程序包管理器"--》”程序包管理器控制台“,打開后輸出”Install-Package Microsoft.AspNet.Identity.Samples -Version 2.0.0-beta2 –Pre“ 。然后程序會自動為你安裝Sample程序,期間假如你的其他nuget包版本過低,或有重復的文件,會提示你更新等,你可能需要輸入Y並按回車。另外需要注意的是:這個SAMPLE包需要安裝在一個Empty的MVC項目中。
2.0的腳手架內容和組織架構比以前更復雜,基本的內容您可以查看樣板程序,此處不再贅述。下面僅講解實現郵箱認證功能需要進行的改動。
提示:此示例需要您對Identity有基本的了解,並配合Identity 2.0的Sample Code閱讀效果更佳。
========================================================================================
1.配置smtp服務
要發送郵件給注冊用戶,首先我們需要有個發件郵箱,這里可以隨便弄個QQ郵箱之類的。配置的內容可以直接寫死在代碼里,但為了方便配置,我們把它寫到Web.Config中,

1 <configSections> 2 ... 3 <sectionGroup name="application"> 4 ... 5 <section name="mail" type="DotNetRocks.Web.Configurations.MailConfig" allowLocation="true" 6 allowDefinition="Everywhere" requirePermission="false" /> 7 ... 8 </sectionGroup> 9 ... 10 </configSections> 11 ... 12 <application> 13 ... 14 <!-- 測試時可將requireValid 設為false 則不進行郵箱驗證--> 15 <mail requireValid="true" server="smtp.qq.com" port="25" uid="something@qq.com" pwd="yourpassword" enableSSL="false" enablePwdCheck="false" /> 16 ... 17 </application> 18 ...
上面的代碼中,為了自定義配置節點,需要在configSections節點中,聲明我們自定義的節點,這里我們自定義了一組節點叫application。然后我們就可以在下文中詳細配置application節點組,其中的mail節點就是和smtp相關的郵箱配置。其中最主要的屬性就是smtp服務器地址,端口號(一般默認25),和你的郵箱賬號和密碼。其他內容可以根據你的需要的策略自行配置。
配置寫好了,在程序中要使用時,只需讀出其中的數據即可。我們需要將配置讀到一個模型中,因此新建一個"Configurations"文件夾,並新建一個"MailConfig"類讓它繼承ConfigurationSection類,代碼如下。

1 public class MailConfig : ConfigurationSection 2 { 3 /// <summary> 4 /// 注冊時是否需要驗證郵箱 5 /// </summary> 6 [ConfigurationProperty("requireValid", DefaultValue = "false", IsRequired = true)] 7 public bool RequireValid 8 { 9 get 10 { 11 return (bool)this["requireValid"]; 12 } 13 set 14 { 15 this["requireValid"] = value; 16 } 17 } 18 /// <summary> 19 /// SMTP服務器 20 /// </summary> 21 [ConfigurationProperty("server", IsRequired = true)] 22 public string Server 23 { 24 get 25 { 26 return (string)this["server"]; 27 } 28 set 29 { 30 this["server"] = value; 31 } 32 } 33 /// <summary> 34 /// 默認端口25(設為-1讓系統自動設置) 35 /// </summary> 36 [ConfigurationProperty("port", DefaultValue = "25", IsRequired = true)] 37 public int Port 38 { 39 get 40 { 41 return (int)this["port"]; 42 } 43 set 44 { 45 this["port"] = value; 46 } 47 } 48 /// <summary> 49 /// 賬號 50 /// </summary> 51 [ConfigurationProperty("uid", IsRequired = true)] 52 public string Uid 53 { 54 get 55 { 56 return (string)this["uid"]; 57 } 58 set 59 { 60 this["uid"] = value; 61 } 62 } 63 /// <summary> 64 /// 密碼 65 /// </summary> 66 [ConfigurationProperty("pwd", IsRequired = true)] 67 public string Pwd 68 { 69 get 70 { 71 return (string)this["pwd"]; 72 } 73 set 74 { 75 this["pwd"] = value; 76 } 77 } 78 /// <summary> 79 /// 是否使用SSL連接 80 /// </summary> 81 [ConfigurationProperty("enableSSL", DefaultValue = "false", IsRequired = false)] 82 public bool EnableSSL 83 { 84 get 85 { 86 return (bool)this["enableSSL"]; 87 } 88 set 89 { 90 this["enableSSL"] = value; 91 } 92 } 93 /// <summary> 94 /// 95 /// </summary> 96 [ConfigurationProperty("enablePwdCheck", DefaultValue = "false", IsRequired = false)] 97 public bool EnablePwdCheck 98 { 99 get 100 { 101 return (bool)this["enablePwdCheck"]; 102 } 103 set 104 { 105 this["enablePwdCheck"] = value; 106 } 107 }
在使用時,只需要MailConfig config = (MailConfig)ConfigurationManager.GetSection("application/mail"); 即可讀取到配置屬性,更詳細的內容可以在ConfigurtaionSection上按F1參考MSDN。
2.配置UserManager
在項目腳手架中,App_Start文件夾下有個IdentityConfig.cs文件,打開他,其中有一個繼承了UserManager<ApplicationUser>的ApplicationUserManage類。(如果你沒有變更文件結構的話,當然你可以根據需求自行調整整個文件組織結構)。
在ApplicationUserManager中可以配置的東西很多,比如賬號鎖定規則,密碼強度規則,密保登陸等。其中還有一句manager.EmailService = new EmailService();, 而這個EmailService類就在本文件中,他繼承了IIdentityMessageService接口,這個接口總共只有一個方法SendAsync,也就是發送郵件,我們只需要在這個方法中實現發送郵件的邏輯,在需要發送郵件時UserManager會自動調用該方法。

1 /// <summary> 2 /// 郵箱驗證Service 3 /// </summary> 4 public class EmailService : IIdentityMessageService 5 { 6 public async Task SendAsync(IdentityMessage message) 7 { 8 MailConfig mailConfig = (MailConfig)ConfigurationManager.GetSection("application/mail"); 9 if (mailConfig.RequireValid) 10 { 11 // 設置郵件內容 12 var mail = new MailMessage( 13 new MailAddress(mailConfig.Uid, "no-reply"), 14 new MailAddress(message.Destination) 15 ); 16 mail.Subject = message.Subject; 17 mail.Body = message.Body; 18 mail.IsBodyHtml = true; 19 mail.BodyEncoding = Encoding.UTF8; 20 // 設置SMTP服務器 21 var smtp = new SmtpClient(mailConfig.Server, mailConfig.Port); 22 smtp.Credentials = new System.Net.NetworkCredential(mailConfig.Uid, mailConfig.Pwd); 23 24 await smtp.SendMailAsync(mail); 25 } 26 await Task.FromResult(0); 27 } 28 }
上面這段代碼簡單的實現了郵件發送邏輯,當然也可以有更復雜的策略,比如是否使用SSL連接等此處我並未配置。關於電子郵件的相關知識可以參考:http://systemnetmail.com/
代碼寫到這里,運行程序,嘗試注冊新用戶,如果郵箱配置沒有問題的話,新用戶應該已經可以收到系統發來的郵件了。不過現在即使未驗證郵箱也可以登陸,需要自己實現未驗證郵箱禁止登陸的功能。
3.變更Login策略
首先說一下,在注冊功能中,為了方便測試,注冊后跳轉的頁面直接提供了激活郵箱的連接,正式運行的話需要把它刪除,我們只需要把該連接發送到用戶郵箱即可。
在登陸時,統一調用的是SingInHelper中的PasswordSignIn方法,然后返回枚舉類型的SignInStatus,來決定將哪個頁面返回給用戶。現在SignInStatus中並沒有郵箱未驗證禁止登陸的狀態。因此我們要在其中加一個,比如叫”InvalidEmail"。如果返回的是SignInStatus.InvalidEmail,則讓用戶跳轉到提示郵箱未激活的界面。也就是在Login方法中的switch語句中加一個case,如下圖

1 public async Task<ActionResult> Login(LoginViewModel model, string returnUrl){ 2 //...此處省略若干代碼 3 switch (result) 4 { 5 case SignInStatus.Success: 6 return RedirectToLocal(returnUrl); 7 case SignInStatus.LockedOut: 8 return View("Lockout"); 9 case SignInStatus.InvalidEmail: 10 return View("DisplayEmail"); 11 case SignInStatus.RequiresTwoFactorAuthentication: 12 return RedirectToAction("SendCode", new { ReturnUrl = returnUrl }); 13 case SignInStatus.Failure: 14 default: 15 ModelState.AddModelError("", "Invalid login attempt."); 16 return View(model); 17 } 18 }
這里,我直接跳轉到DisplayEmail頁面,提示用戶未激活郵箱,禁止登陸,並詢問他是否需要再次發送驗證郵件。當然這個頁面我自己做了需要的修改。
然后我們需要在PasswordSignIn方法中也加入相應的策略以使它能夠返回InvalidEmail狀態值。

1 public async Task<SignInStatus> PasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout) 2 { 3 var user = await UserManager.FindByNameAsync(userName); 4 if (user == null) 5 { 6 return SignInStatus.Failure; 7 } 8 if (await UserManager.IsLockedOutAsync(user.Id)) 9 { 10 return SignInStatus.LockedOut; 11 } 12 if (!await UserManager.IsEmailConfirmedAsync(user.Id)) 13 { 14 return SignInStatus.InvalidEmail; 15 } 16 if (await UserManager.CheckPasswordAsync(user, password)) 17 { 18 return await SignInOrTwoFactor(user, isPersistent); 19 } 20 if (shouldLockout) 21 { 22 // If lockout is requested, increment access failed count which might lock out the user 23 await UserManager.AccessFailedAsync(user.Id); 24 if (await UserManager.IsLockedOutAsync(user.Id)) 25 { 26 return SignInStatus.LockedOut; 27 } 28 } 29 return SignInStatus.Failure; 30 }
在上面的代碼中,未加郵箱驗證前,用戶登錄后的邏輯順序,1.判斷是否存在該賬號,2.判斷該賬號是否鎖定,3.檢測賬號密碼是否正確(若正確直接登陸,否則失敗次數+1),4(若走到這一步則說明賬號密碼錯誤)檢測是否需要鎖定賬號,5返回登陸失敗。根據上面的邏輯,我們應該把郵箱驗證加到2和3之間,也就是如上圖代碼中調用UserManager的IsEmailConfirmedAsync方法,來驗證用戶郵箱是否認證。
至此,整個功能應該已經全部實現了。
================================================================================
此文是在我已經實現后第二天所寫,若步驟有遺漏或錯誤,歡迎指正補充