原文出自 Rui Figueiredo 的部落格,原文連結 《ASP.NET Identity Core From Scratch》
譯者注:這篇博文釋出時正值Asp.Net Core 1.1 時期,原部落客使用的是 vs code+ yeoman+ node.js。現在(2017年12月22日)已經到了Asp.Net Core 2.0時代了, 文中的代碼發生了變化,yeoman上也找不到最新2.0的模闆,是以在此譯文中,部落客對其作了改動,開發工具由 vs code+yeoman+node.js 替換為vs code+dotnet cli,.net core sdk 從1.x更新到2.x,這篇文章有些内容和原文不一樣的,但是整體思路沒變。動手做本文中的例子請準備好上述條件,鑒于上述的工具及軟體,你可以在windows linux osX進行編碼。如果你在windows上使用visual studio 2017 開發,那麼參照本文章完全沒問題
摘要:本文主要介紹了,在不使用visual studio模闆的情況下,如何一步一步的将 Asp.Net Core Identity引入到你的項目中,在這些步驟中,你将知道 使用Identity的必要步驟,以及依賴,學習一個架構,沒什麼比從0開始做一遍更好的了。
能夠讓使用者在你的網站上建立帳戶往往是建立網站的第一步。
雖然這是一個看起來很平凡的任務,但它涉及到很多工作,而且很容易出錯。
雖然你可以使用Visual Studio附帶的模闆(如果你不使用Windows,也可以使用yeoman模闆譯者注:在譯文中使用的dotnet cli),但是模闆隻會給你一個不完整的解決方案,你必須自己設定電子郵件确認。
這些模闆往往會隐藏許多的細節,是以如果出現問題,而且你不了解這些細節,那麼你将很難找到問題的根源。
這篇部落格是一個從零(我們會從 empty web application 模闆開始)配置Identity的step by step指導。這篇部落格實作使用者建立賬戶,郵件确認,以及密碼重置。你可以在
這裡找到最終的項目代碼。
你可以使用 git克隆這個檔案庫:
git clone https://github.com/ruidfigueiredo/AspNetIdentityFromScratch
本指南假定你使用的是Visual Studio Code,以便可以在Windows,Mac或Linux中操作。如果你正在使用Visual Studio的完整版本,那麼也是沒問題的。
我們假設你已經安裝了
.Net Core 運作時及 Sdk。
1 . 建立一個空的Web應用程式
首先打開cmd然後移動到某個你要建立項目的目錄,注意目錄的最後一級是将要建立項目的名稱,稍後将使用 dotnet cli在這裡建立項目,它看起來可能是這樣的:
D:\IdentityFromScratch
然後在cmd中輸入
dotnet new web
得到的結果類似下面這樣:
已成功建立模闆“ASP.NET Core Empty”。
此模闆包含非 Microsoft 的各方的技術,有關詳細資訊,請參閱 https://aka.ms/template-3pn。
正在處理建立後操作...
正在 D:\IdentityFromScratch\IdentityFromScratch.csproj 上運作 "dotnet restore"...
Restoring packages for D:\IdentityFromScratch\IdentityFromScratch.csproj...
Generating MSBuild file D:\IdentityFromScratch\obj\IdentityFromScratch.csproj.nuget.g.props.
Generating MSBuild file D:\IdentityFromScratch\obj\IdentityFromScratch.csproj.nuget.g.targets.
Restore completed in 3.04 sec for D:\IdentityFromScratch\IdentityFromScratch.csproj.
還原成功。
使用vs code 打開項目所在的檔案夾(在本文中是
D:\IdentityFromScratch\
),vs code 将提示
Required assets to build and debug are missing from 'IdentityFromScratch'. Add them?
,vs code 在詢問你是否添加調試資源,選擇
yes
,vs code将會向目錄中添加 .vscode檔案夾,裡面含有調試相關的配置檔案。
打開Startup.cs并添加使用MVC所需的Ioc配置。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
另外,更新Configure方法,以便将MVC中間件添加到管道,這裡使用預設路由。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
如果你不熟悉ASP.NET Core中的中間件和IoC,那麼
中間件文檔和
依賴注入文檔能幫助你快速了解他們。
因為我們使用的是預設路由,是以如果對我們網站的根目錄進行http請求,那麼要調用的控制器就是HomeController。控制器的action将是Index,是以讓我們建立這些必要的東西。
首先在vs code中根目錄(IdentityFromScratch)下建立一個名為Controllers的檔案夾(空模闆裡沒有)。
然後建立HomeController.cs檔案,
檔案内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace AspNetIdentityFromScratch.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
然後再在跟根目錄下建立Views檔案夾,在Views下建立Shared檔案夾。
在Shared檔案夾下建立 _Layout.cshtml 檔案,檔案的内容很簡單:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
@RenderBody()
</body>
</html>
_Layout.cshtml 檔案是一個布局檔案,裡面的
@RenderBody()
将我們的視圖渲染在此處。
在MVC中,還有一個特殊的檔案它會在所有View之前自動加載,它就是 _ViewStart.cshtml,在Views檔案夾下建立它,其中的代碼很少,如下:
@{
Layout = "_Layout";
}
這行代碼将預設布局設定為我們剛剛建立的布局。
此外,因為稍後我們将使用ASP.NET Core的一個名為tag helper的新功能,是以我們将使用另一個檔案(它是檔案名_ViewImports.cshtml)為所有視圖啟用它們。在Views檔案夾下建立_ViewImports.cshtml,其内容如下:
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
_ViewImports.cshtml允許我們添加
@using
語句,例如
@using System.Collections.Generic
,
以便在_ViewImports所在的檔案夾及其子檔案夾中的所有Razor視圖中直接使用ViewImprots檔案中引用的命名空間下的類而不必添加@using語句。是以我們在這裡添加 tag helper之後,所有的視圖中都可以使用它。
我們仍然需要 Index action的視圖,接下來在Views下建立Home檔案夾,然後建立Index.cshtml視圖檔案,檔案内容如下:
<h1>Home</h1>
@if (User.Identity.IsAuthenticated)
{
<p>User @User.Identity.Name is signed in. <a href="/Account/Logout">Logout</a> </p>
}
else
{
<p>No user is signed in.</p>
}
<div>
<a href="/Account/Login">Login</a>
</div>
<div>
<a href="/Account/Register">Register</a>
</div>
<div>
<a href="/Account/ForgotPassword">Forgot Password</a>
</div>
運作該項目,你應該看到一個非常簡單的頁面,提醒你沒有使用者登入。裡面裡的連結也無法正常工作,因為我們稍後才會處理它們。
譯者注:在vs code 中按下快捷鍵
Ctrl+~
來啟動終端,
鍵入
即可運作項目,如果不行的話先運作
dotnet run
dotnet build
ASP.NET Core Identity
ASP.NET Core Identity是ASP.NET Core的成員系統,它提供了管理使用者帳戶所需的功能。通過使用它,我們将能夠建立使用者并生成token以用于電子郵件确認和密碼重置。
你需要添加下面兩個Nuget包:
Microsoft.AspNetCore.Identity
Microsoft.AspNetCore.Identity.EntityFrameworkCore
譯者注:當我們使用dotnet cli建立項目是,預設添加了,這個包中含有所有這篇文章所需的包(除非我标記了不能忽略),是以本文中隻做介紹,下文同理。另外你可以打開項目的
Microsoft.AspNetCore.All
檔案檢視與編輯項目的包,這和1.x版本是不一樣的。
.csproj
在繼續下一步之前,請注意有關ASP.NET Core Identity如何存儲使用者的資訊的。一個使用者将具有(至少)名為
IdentityUser
的類中包含的所有屬性。你可以建立
IdentityUser
的子類并添加更多屬性。當我們實際建立資料庫時,這些将出現在使用者的表中。對于本文中的例子,我們将隻使用預設的
IdentityUser
因為我們需要一個資料庫來存儲我們的使用者資料,這裡我們将使用SQLite(如果你有興趣使用不同的資料庫,請檢視這篇文章:
Cross platform database walk-through using ASP.NET MVC and Entity Framework Core)。
Identity使用SqLite存儲資料需要添加Nuget包:
Microsoft.EntityFrameworkCore.Sqlite
然後打開項目的.csproj檔案,在 ItemGroup中添加(譯者注:這裡不能忽略,這裡添加了Ef的指令行工具):
Microsoft.EntityFrameworkCore.Tools.DotNet
Microsoft.EntityFrameworkCore.Tools
最終的代碼看起來是這樣的:
...
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
</ItemGroup>
...
Configuring ASP.NET Core Identity
一般來講,使用Identity 的話我們假定你的使用者類是繼承于
IdentityUser
的。然後你可以在你的使用者類中添加額外的使用者資訊。你應用程式的
DbContex
也應該繼承與
IdentityDbContext
,之後你可以在其中指定額外的
DbSet
然後尴尬的事情發生了,有很多理由讓你别去這樣做。首先,ASP.NET Core Identity 不是唯一可用的成員系統,甚至不同版本的Identity也存在着很大的差異。是以,如果你完全将你的使用者類和DbContext與ASP.NET Core Identity綁定,則實際上将它們綁定到特定版本的Identity。
讓所有這些工作無需将你的項目綁定到Identity上是一件額外的工作,接下來我們會做這些事。
首先,我們需要更改Startup.cs并添加必要的IoC配置:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<IdentityDbContext>(options =>
{
options.UseSqlite(
"Data Source=users.sqlite",
builder =>
{
builder.MigrationsAssembly("AspNetIdentityFromScratch");
});
});
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityDbContext>()
.AddDefaultTokenProviders();
// services.AddTransient<IEmailSender, EmailSender>();
}
我們向Ioc容器中注冊了
IdentityDbContext
并使用
Data Source = users.sqlite
作為連接配接字元串。當我們建立資料庫時,會在項目的輸出目錄中建立一個名為users.sqlite的檔案(稍後會詳細介紹)。
請注意
UseSqlite
擴充方法中使用的第二個參數:
builder =>
{
builder.MigrationsAssembly("IdentityFromScratch");
});
這段代碼是必需的,因為當我們運作工具(dotnet ef)來建立資料庫遷移時,将執行檢查來驗證IdentityDbContext類是否與你運作工具的項目位于相同的程式集中。如果不是這樣,你可能會得到一個錯誤:
Your target project 'IdentityFromScratch' doesn't match your migrations assembly 'Microsoft.AspNetCore.Identity.EntityFrameworkCore'
...
這就是為什麼我們需要使用UseSqlite的第二個參數。 IdentityFromScratch是我用于項目的名稱,你需要将其更新為你用于你項目的名稱。
接下來是注冊Identity:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityDbContext>()
.AddDefaultTokenProviders();
這裡我們指定了我們想要用于使用者的類
IdentityUser
和角色
IdentityRole
。如前所述,你可以指定你自己的
IdentityUser
的子類,但是如果你這樣做,還需要更改DbContext的注冊方式,例如:
services.AddDbContext<IdentityDbContext<YourCustomUserClass>>(...
services.AddIdentity<YourCustomUserClass, IdentityRole>()
.AddEntityFrameworkStores<IdentityDbContext<YourCustomUserClass>>(...
你可能注意到我沒有提到關于IdentityRole的任何事情。你可以忽略這個,因為你可以添加角色claim來取代role。這裡指定IdentityRole我猜這隻是因為在舊成員系統中role的概念非常突出,不過在我們普遍使用claim的今天,role已經不那麼重要了。另外,指定一個自定義的IdentityRole會使事情變得更加複雜,我将向你展示如何使用claim将role添加到你的使用者。
接下來:
.AddEntityFrameworkStores<IdentityDbContext>()
這僅僅指定使用哪個DbContext,最後:
.AddDefaultTokenProviders();
TokenProvider是生成确認電子郵件和重置密碼token的元件,稍後我們将用到它們。
現在我們隻需要更新管道:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//...
app.UseIdentity();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
使用
UseIdentity
擴充方法時會發生什麼呢?這實際上它會設定cookie中間件,cookie中間件做的是當MVC中的controller action傳回401響應時将使用者重定向到登入頁面,并且在使用者登入并建立身份驗證cookie後,将其轉換為ClaimsPrincipal和ClaimsIdentity,也就是在你使用Request.User時擷取到的實際對象。
生成存儲使用者資料的資料庫
在使用新版本的EF時,生成資料庫基本上隻能使用migrations。接下來我們需要建立一個遷移,然後讓工具生成資料庫。
打開vscode中的終端(上文中有提到過),輸入下面的指令:
dotnet ef migrations add Initial
這将建立名為“Initial”的遷移,使用下面的指令應用它:
dotnet ef database update
該指令所做的是應用所有pending的遷移,在本例情況下,它隻會是“Initial”,它将建立ASP.NET Core Identity所必需的表。
現在應該在輸出檔案夾中有一個名為users.sqlite的檔案。
你當然也可以以程式設計方式生成資料庫(但你始終需要首先建立遷移)。
這樣,如果你想與其他人分享你的項目,他們将不必運作
dotnet ef database update
就能運作該項目。
為實作此目标,在Startup.cs的配置方法中添加:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IdentityDbContext dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
dbContext.Database.Migrate(); // 如果資料庫不存在則會被建立
}
//...
}
不過你一定要在設定為“development”的環境中運作。通常情況下,如果你從Visual Studio運作那麼就是development,但是如果你決定從指令行運作的話:
dotnet run --environment "Development"
發送确認郵件
為了啟用電子郵件驗證和密碼重置,我們要讓應用程式能夠發送電子郵件。當你正處在開發階段時,最簡單的方法就是把郵件資訊存到磁盤檔案上。這樣可以節省你在嘗試操作時不得不等待電子郵件的時間。首先我們需要做的是建立一個接口,我們将其命名為
IMessageService
:
public interface IMessageService
{
Task Send(string email, string subject, string message);
}
寫郵件到檔案的實作類:
public class FileMessageService : IMessageService
{
Task IMessageService.Send(string email, string subject, string message)
{
var emailMessage = $"To: {email}\nSubject: {subject}\nMessage: {message}\n\n";
File.AppendAllText("emails.txt", emailMessage);
return Task.FromResult(0);
}
}
最後一件事是在Startup.cs的ConfigureServices中注冊這個服務:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddTransient<IMessageService, FileMessageService>();
}
使用者注冊頁面
對于這個示範,我們将建構最簡單的Razor視圖,因為我們的目的是展示如何使用ASP.NET Core Identity。
在Controllers檔案夾中建立AccountController。
在構造函數中添加UserManager
public class AccountController : Controller
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IMessageService _messageService;
public AccountController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IMessageService messageService)
{
this._userManager = userManager;
this._signInManager = signInManager;
this._messageService = messageService;
}
//...
UserManager是用來建立使用者和生成驗證token的類。SignInManager 提供密碼驗證的功能,以及使用者的登入與登出(通過處理authentication cookie)。
建立注冊方法:
public IActionResult Register()
{
return View();
}
以及 Views/Account/Register.cshtml 檔案:
<form method="POST">
<div>
<label >Email</label>
<input type="email" name="email"/>
</div>
<div>
<label>Password</label>
<input type="password" name="password"/>
</div>
<div>
<label>Retype password</label>
<input type="password" name="repassword"/>
</div>
<input type="submit"/>
</form>
<div asp-validation-summary="All"></div>
值得注意的是
asp-validation-summary
tag helper,tag helper是ASP.NET Core中的新功能。它們用來替代舊的HtmlHelpers。
validation summary
tag helper,設定的值是
All
,是以它将顯示所有模型錯誤。我們将使用它來顯示建立使用者時檢測到的任何錯誤(例如,使用者名已被占用或密碼不比對)。
下面是使用者系統資料庫單接收的實際處理方法:
[HttpPost]
public async Task<IActionResult> Register(string email, string password, string repassword)
{
if (password != repassword)
{
ModelState.AddModelError(string.Empty, "兩次輸入的密碼不一緻");
return View();
}
var newUser = new IdentityUser
{
UserName = email,
Email = email
};
var userCreationResult = await _userManager.CreateAsync(newUser, password);
if (!userCreationResult.Succeeded)
{
foreach(var error in userCreationResult.Errors)
ModelState.AddModelError(string.Empty, error.Description);
return View();
}
var emailConfirmationToken = await _userManager.GenerateEmailConfirmationTokenAsync(newUser);
var tokenVerificationUrl = Url.Action("VerifyEmail", "Account", new {id = newUser.Id, token = emailConfirmationToken}, Request.Scheme);
await _messageService.Send(email, "驗證電子郵件", $"點 <a href=\"{tokenVerificationUrl}\">我</a> 驗證郵件");
return Content("請檢查你的郵箱,我們向你發送了郵件。");
}
要建立我們的新使用者,我們首先必須建立
IdentityUser
的執行個體。由于我們用email作為使用者名,是以我們為其指定了相同的值。
然後我們調用
_userManager.CreateAsync(newUser,password);
它的傳回值中有一個名為
Success
的
bool
值屬性,它代表了使用者是否建立成功。裡面的另一個屬性
Errors
中含有建立失敗的原因(例如:不符合密碼要求,使用者名已被占用等)。
此時,如果
userCreatingResult.Success
為
true
,則使用者已經建立。但是,如果你要檢查
newUser.EmailConfirmed
它将傳回
false
因為我們要驗證電子郵件,是以我們可以使用
_userManager
GenerateEmailConfirmationTokenAsync
生成一個電子郵件确認token。然後,我們使用IMessagingService來“發送”它。
如果你現在運作項目并轉到Account/Register建立一個新使用者,那麼在此過程之後,你的項目檔案夾中應該有一個名為emails.txt的檔案,裡面含有确認連結。
我們會建立 email confirmation controller action,之後你就可以複制檔案中的電子郵件确認網址,然後驗證電子郵件(稍後将做這些)。
将角色添加為Claim
上文提到你不需要過多的關注
IdentityRole
,因為你可以将角色添加為Claim,以下是如何将“Administrator”角色添加到使用者的示例:
await _userManager.AddClaimAsync(identityUser, new Claim(ClaimTypes.Role, "Administrator"));
然後,你可以像以前一樣在controller action上要求“Administrator”角色:
[Authorize(Roles="Administrator")]
public IActionResult RequiresAdmin()
{
return Content("OK");
}
郵件驗證
讓我們來建立controller action,當使用者點選電子郵件中的連結時将會調用該操作:
public async Task<IActionResult> VerifyEmail(string id, string token)
{
var user = await _userManager.FindByIdAsync(id);
if(user == null)
throw new InvalidOperationException();
var emailConfirmationResult = await _userManager.ConfirmEmailAsync(user, token);
if (!emailConfirmationResult.Succeeded)
return Content(emailConfirmationResult.Errors.Select(error => error.Description).Aggregate((allErrors, error) => allErrors += ", " + error));
return Content("Email confirmed, you can now log in");
}
我們需要使用使用者的ID來加載
IdentityUser
。有了這個和電子郵件确認token,我們可以調用
userManager
ConfirmEmailAsync
方法。如果token是正确的,則使用者資料庫表中的
EmailConfirmed
屬性将被更新以訓示使用者的電子郵件已經确認。
出于簡化示例的目的,我們使用了
Content
方法。你可以建立一個新視圖,然後加一個跳轉到登入頁面的連接配接。有一件事你不應該做的就是在确認之後自動登入使用者。
這是因為如果使用者多次點選确認連結,都不會引發任何錯誤。
如果你想在确認電子郵件後自動登入使用者,則該連結本質上将成為登入的一種方式,而且不需要使用密碼,又能多次使用。
登入頁面
首先在AccountController中建立一個Login方法來處理GET請求:
public IActionResult Login()
{
return View();
}
以及它的視圖 Views/Account/Login.cstml:
<form method="POST">
<div>
<label>Email</label>
<input type="email" name="email"/>
</div>
<div>
<label>Password</label>
<input type="password" name="password"/>
</div>
<input type="submit"/>
</form>
<div asp-validation-summary="All"></div>
控制器處理POST請求的操作:
[HttpPost]
public async Task<IActionResult> Login(string email, string password, bool rememberMe)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login");
return View();
}
if (!user.EmailConfirmed)
{
ModelState.AddModelError(string.Empty, "Confirm your email first");
return View();
}
var passwordSignInResult = await _signInManager.PasswordSignInAsync(user, password, isPersistent: rememberMe, lockoutOnFailure: false);
if (!passwordSignInResult.Succeeded)
{
ModelState.AddModelError(string.Empty, "Invalid login");
return View();
}
return Redirect("~/");
}
這裡我們使用
UserManager
通過電子郵件(
FindByEmailAsync
)來檢索使用者。如果電子郵件未被确認,我們不允許使用者登入,在這種情況下你還提供重新發送驗證郵件的選項。
我們将使用
SignInManager
将使用者登入。它的
PasswordSignInAsync
方法需要
IdentityUser
,正确的密碼,标志
isPersistent
和另一個标志
lockoutOnFailure
這個方法所做的是發起建立加密cookie,cookie将被發送到使用者的計算機上,cookie中含有該使用者的所有claims。成功登入後,你可以使用Chrome的開發人員工具檢查此Cookie。
isPersistent參數将決定Cookie的Expires屬性的值。
如果設定為true(截圖中就是這種情況),cookie的過期日期會是幾個月之後(此時間範圍是可配置的,請參閱配置部分)。當設定為false時,cookie将Expires屬性設定為session,這意味着在使用者關閉浏覽器之後cookie将被删除。
lockoutOnFailure
标志将在使用者多次登入失敗之後阻止使用者登入。使用者被鎖定多少次以及多長時間是可配置的(我們将在配置部分提到這一點)。如果你決定使用
lockoutOnFailure
,請注意,每次使用者登入失敗時都需要調用
_userManager.AccessFailedAsync(user)。
我忽略了一件事情,不過它很簡單,那就是returnUrl。如果你想,你可以添加一個參數到Login方法,這樣當使用者成功登入時,你可以發送一個重定向到return url。不過你應該檢查url是否是local的(即連接配接是指向你的應用程式,而不是别人的),為此,在controller action中可以執行以下操作:
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}else
{
return Redirect("~/");
}
密碼重置
對于密碼重置,我們需要一個頁面。
首先controller action,我們命名為
ForgotPassword
public IActionResult ForgotPassword()
{
return View();
}
視圖 /Views/Account/ForgotPassword.cshtml:
<form method="POST">
<div>
<label>Email</label>
<input type="email" name="email"/>
</div>
<input type="submit"/>
</form>
處理Post請求的方法:
[HttpPost]
public async Task<IActionResult> ForgotPassword(string email)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
return Content("Check your email for a password reset link");
var passwordResetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
var passwordResetUrl = Url.Action("ResetPassword", "Account", new {id = user.Id, token = passwordResetToken}, Request.Scheme);
await _messageService.Send(email, "Password reset", $"Click <a href=\"" + passwordResetUrl + "\">here</a> to reset your password");
return Content("Check your email for a password reset link");
}
你可能會注意到,無論電子郵件是否屬于一個存在的使用者,使用者都将看到相同的消息。
你應該這樣做,因為如果你不這樣做,則可以使用此功能來發現使用者是否在你的站點中擁有帳戶。
控制器的其餘部分隻是生成token并發送電子郵件。
我們仍然需要建構
ResetPassword
controller action來處理我們用電子郵件發送給使用者的連結。
[HttpPost]
public async Task<IActionResult> ResetPassword(string id, string token, string password, string repassword)
{
var user = await _userManager.FindByIdAsync(id);
if (user == null)
throw new InvalidOperationException();
if (password != repassword)
{
ModelState.AddModelError(string.Empty, "Passwords do not match");
return View();
}
var resetPasswordResult = await _userManager.ResetPasswordAsync(user, token, password);
if (!resetPasswordResult.Succeeded)
{
foreach(var error in resetPasswordResult.Errors)
ModelState.AddModelError(string.Empty, error.Description);
return View();
}
return Content("Password updated");
}
如果你想知道為什麼我沒有在表單中添加token和使用者ID作為隐藏字段,這是因為MVC model binder會在url參數中找到它們。不過大多數示例(以及來自Visual Studio的個人使用者帳戶的預設模闆)都将其添加為隐藏字段。
這個action沒有太多内容,隻是使用
_userManager.ResetPasswordAsync
為使用者設定一個新的密碼。
登出登陸
登出一個使用者隻需要使用
SignInManager
并調用
SignOutAsync
[HttpPost]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return Redirect("~/");
}
僅僅允許使用者使用Http Post 方式登出絕對是個更好的方法。 請注意,為了簡單起見,我在示例中沒有這樣做。是以如果你照着做的話請不要添加[HttpPost](Index頁面有連結到/Account/Logout),或者将Index.cshtml中的登出連結更改為發送到/Account/Logout的表單。
配置
你可能想知道,如果我選擇與AccountController不同的名稱,所有這些代碼還能工作嗎?也許你會将登入action命名為SignIn而不是Login。
如果你這樣做是行不通的。例如,如果你在controller action中使用[Authorize]屬性,并且使用者導航到該操作的URL,則重定向将發送到 /Account/Login。
那麼你怎麼能改變設定呢?答案就是在Startup.cs的ConfigureServices中注冊Identity服務的時候指定配置。
以下是如何更改預設的登入頁面:
services.ConfigureApplicationCookie(o =>
{
o.LoginPath = "/Account/SignIn";
});
設定密碼要求,例如:
services.AddIdentity<IdentityUser, IdentityRole>(
options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
}
)
這将設定密碼限制為最少6個字元。
你還可以要求确認的電子郵件:
services.Configure<IdentityOptions>(options => {
options.SignIn.RequireConfirmedEmail = true;
});
如果這隻了要求電子郵件确認,那麼你将不必檢查
IdentityUser
EmailConfirmed
屬性。當你嘗試使用
SignInManager
對使用者進行登入時,會登入失敗,并且結果将包含名為
IsNotAllowed
的屬性,而且它的值為
true
還有其他配置選項可供使用,例如帳戶被鎖定之前允許的嘗試次數以及被鎖定多長時間。
使用 SendGrid 真正的發送郵件
如果你真的想發送電子郵件,你可以使用
SendGrid。這是一個電子郵件服務,他們有一個免費的Plan,是以你可以試試看。
你需要安裝它的nuget包(譯者注:這個也是不能忽略的):
SendGrid.NetCore
以下是使用SendGrid的IMessageService的實作:
public class SendGridMessageService : IMessageService
{
public async Task Send(string email, string subject, string message)
{
var emailMessage = new SendGrid.SendGridMessage();
emailMessage.AddTo(email);
emailMessage.Subject = subject;
emailMessage.From = new System.Net.Mail.MailAddress("senderEmailAddressHere@senderDomainHere", "info");
emailMessage.Html = message;
emailMessage.Text = message;
var transportWeb = new SendGrid.Web("PUT YOUR SENDGRID KEY HERE");
try{
await transportWeb.DeliverAsync(emailMessage);
}catch(InvalidApiRequestException ex){
System.Diagnostics.Debug.WriteLine(ex.Errors.ToList().Aggregate((allErrors, error) => allErrors += ", " + error));
}
}
}
然後更新
ConfigureService
services.AddTransient<IMessageService, SendGridMessageService>();
全文完