原文出自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,这个包中含有所有这篇文章所需的包(除非我标记了不能忽略),所以本文中只做介绍,下文同理。另外你可以打开项目的.csproj文件查看与编辑项目的包,这和1.x版本是不一样的。

在继续下一步之前,请注意有关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 、SignInManager (这些依赖项在Startup.cs中的ConfigureServices中添加services.AddIdentity <...>时被注册)和IMessageService:

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);它的返回值中有一个名为Successbool值属性,它代表了用户是否创建成功。里面的另一个属性Errors中含有创建失败的原因(例如:不符合密码要求,用户名已被占用等)。

此时,如果userCreatingResult.Successtrue,则用户已经创建。但是,如果你要检查newUser.EmailConfirmed它将返回false

因为我们要验证电子邮件,所以我们可以使用_userManagerGenerateEmailConfirmationTokenAsync生成一个电子邮件确认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,我们可以调用userManagerConfirmEmailAsync方法。如果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并发送电子邮件。

我们仍然需要构建ResetPasswordcontroller 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;
});

如果这只了要求电子邮件确认,那么你将不必检查IdentityUserEmailConfirmed属性。当你尝试使用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>();

全文完

【译】ASP.NET Identity Core 从零开始的更多相关文章

  1. 从0引入 ASP.NET Identity Core

    原文出自Rui Figueiredo的博客,原文链接<ASP.NET Identity Core From Scratch> 译者注:这篇博文发布时正值Asp.Net Core 1.1 时 ...

  2. 向空项目添加 ASP.NET Identity

    安装 AspNet.Identity 程序包 Microsoft.AspNet.Identity.Core 包含 ASP.NET Identity 核心接口Microsoft.AspNet.Ident ...

  3. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  4. [译]ASP.NET Core 2.0 系列文章目录

    基础篇 [译]ASP.NET Core 2.0 中间件 [译]ASP.NET Core 2.0 带初始参数的中间件 [译]ASP.NET Core 2.0 依赖注入 [译]ASP.NET Core 2 ...

  5. 译:Asp.Net Identity与Owin,到底谁是谁?

    送给正在学习Asp.Net Identity的你 :-) 原文出自 trailmax 的博客AspNet Identity and Owin. Who is who. Recently I have ...

  6. 【译】Asp.Net Identity与Owin,到底谁是谁?

    送给正在学习Asp.Net Identity的你 :-) 原文出自 trailmax 的博客AspNet Identity and Owin. Who is who. Recently I have ...

  7. 从实体框架核心开始:构建一个ASP。NET Core应用程序与Web API和代码优先开发

    下载StudentApplication.Web.zip - 599.5 KB 下载StudentApplication.API.zip - 11.5 KB 介绍 在上一篇文章中,我们了解了实体框架的 ...

  8. 从Membership 到 .NET4.5 之 ASP.NET Identity

    我们前面已经讨论过了如何在一个网站中集成最基本的Membership功能,然后深入学习了Membership的架构设计.正所谓从实践从来,到实践从去,在我们把Membership的结构吃透之后,我们要 ...

  9. ASP.NET Identity入门系列教程(一) 初识Identity

    摘要 通过本文你将了解ASP.NET身份验证机制,表单认证的基本流程,ASP.NET Membership的一些弊端以及ASP.NET Identity的主要优势. 目录 身份验证(Authentic ...

随机推荐

  1. 老李的blog使用日记(3)

    匆匆忙忙.碌碌无为,这是下一个作业,VS,多么神圣高大上,即使这样,有多少人喜欢你就有多少人烦你,依然逃不了被推销的命运,这抑或是它喜欢接受的,但是作为被迫接受者,能做的的也只有接受,而已. 既来之则 ...

  2. 团队作业四-WBS练习

    我们团队开发的是四则运算,主要面对的用户是小学生.老师及学生家长.经过我们组成员的讨论和结合实际及自身能力,对团队成员分配任务,队长负责全局工作主要负责任务,统一进度,和适量的编码,露哥和阮磊主要负责 ...

  3. beta 圆桌 7

    031602111 傅海涛 1.今天进展 主界面微调,部分地方加入用户体验设计 2.存在问题 文档转化太久 3.明天安排 完成全部接口的交互 4.心得体会 文档转化优化不了 031602115 黄家雄 ...

  4. RocketMQ 事务消息

    RocketMQ 事务消息在实现上充分利用了 RocketMQ 本身机制,在实现零依赖的基础上,同样实现了高性能.可扩展.全异步等一系列特性. 在具体实现上,RocketMQ 通过使用 Half To ...

  5. Windows server 自带的 .net版本

    1. Win2012r2 所带的版本: 2. Win2016 所带的版本 4.6 Win2019 自带的 .net版本为: 4.7 4. 然后比较 Win2008r2sp1 使用的是 .net3.5 ...

  6. BZOJ2303 APIO2011方格染色(并查集)

    比较难想到的是将题目中的要求看做异或.那么有ai,j^ai+1,j^ai,j+1^ai+1,j+1=1.瞎化一化可以大胆猜想得到a1,1^a1,j^ai,1^ai,j=(i-1)*(j-1)& ...

  7. Racket Cheat Sheet

    Racket Cheat Sheet 来源  http://docs.racket-lang.org/racket-cheat/index.html?q=Racket%20Cheat%20Sheet ...

  8. 伤不起:File.toPath() & Paths.get()

    java.nio.file.Path这个类应该是从java7才开始有的. 通过File类有两个方法可以转换成Path. 1. Path p = Paths.get(file.toURI());  // ...

  9. 线段树区间更新 lazy

    1. hdu1698 http://acm.hdu.edu.cn/showproblem.php?pid=1698 /* x y k x~y的值变为k */ #include <cstdio&g ...

  10. Tomcat权威指南-读书摘要系列10

    Tomcat集群 一些集群技术 DNS请求分配 TCP网络地址转换请求分配 Mod_proxy_balance负载均衡与故障复原 JDBC请求分布与故障复原