原文出自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克隆这个档案库:

  1. git clone https://github.com/ruidfigueiredo/AspNetIdentityFromScratch

本指南假定你使用的是Visual Studio Code,以便可以在Windows,Mac或Linux中操作。如果你正在使用Visual Studio的完整版本,那么也是没问题的。

我们假设你已经安装了.Net Core 运行时及 Sdk

1 . 创建一个空的Web应用程序

首先打开cmd然后移动到某个你要创建项目的目录,注意目录的最后一级是将要创建项目的名称,稍后将使用 dotnet cli在这里创建项目,它看起来可能是这样的:

  1. D:\IdentityFromScratch

然后在cmd中输入

  1. dotnet new web

得到的结果类似下面这样:

  1. 已成功创建模板“ASP.NET Core Empty”。
  2. 此模板包含非 Microsoft 的各方的技术,有关详细信息,请参阅 https://aka.ms/template-3pn。
  3. 正在处理创建后操作...
  4. 正在 D:\IdentityFromScratch\IdentityFromScratch.csproj 上运行 "dotnet restore"...
  5. Restoring packages for D:\IdentityFromScratch\IdentityFromScratch.csproj...
  6. Generating MSBuild file D:\IdentityFromScratch\obj\IdentityFromScratch.csproj.nuget.g.props.
  7. Generating MSBuild file D:\IdentityFromScratch\obj\IdentityFromScratch.csproj.nuget.g.targets.
  8. Restore completed in 3.04 sec for D:\IdentityFromScratch\IdentityFromScratch.csproj.
  9. 还原成功。

使用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配置。

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddMvc();
  4. }

另外,更新Configure方法,以便将MVC中间件添加到管道,这里使用默认路由。

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. app.UseStaticFiles();
  8. app.UseMvcWithDefaultRoute();
  9. }

如果你不熟悉ASP.NET Core中的中间件和IoC,那么中间件文档依赖注入文档能帮助你快速了解他们。

因为我们使用的是默认路由,所以如果对我们网站的根目录进行http请求,那么要调用的控制器就是HomeController。控制器的action将是Index,所以让我们创建这些必要的东西。

首先在vs code中根目录(IdentityFromScratch)下创建一个名为Controllers的文件夹(空模板里没有)。

然后创建HomeController.cs文件,

文件内容如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNetCore.Identity;
  6. using Microsoft.AspNetCore.Mvc;
  7. namespace AspNetIdentityFromScratch.Controllers
  8. {
  9. public class HomeController : Controller
  10. {
  11. public IActionResult Index()
  12. {
  13. return View();
  14. }
  15. }
  16. }

然后再在跟根目录下创建Views文件夹,在Views下创建Shared文件夹。

在Shared文件夹下创建 _Layout.cshtml 文件,文件的内容很简单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta name="viewport" content="width=device-width" />
  5. <title>@ViewBag.Title</title>
  6. </head>
  7. <body>
  8. @RenderBody()
  9. </body>
  10. </html>

_Layout.cshtml 文件是一个布局文件,里面的@RenderBody()将我们的视图渲染在此处。

在MVC中,还有一个特殊的文件它会在所有View之前自动加载,它就是 _ViewStart.cshtml,在Views文件夹下创建它,其中的代码很少,如下:

  1. @{
  2. Layout = "_Layout";
  3. }

这行代码将默认布局设置为我们刚刚创建的布局。

此外,因为稍后我们将使用ASP.NET Core的一个名为tag helper的新功能,所以我们将使用另一个文件(它是文件名_ViewImports.cshtml)为所有视图启用它们。在Views文件夹下创建_ViewImports.cshtml,其内容如下:

  1. @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"

_ViewImports.cshtml允许我们添加@using语句,例如@using System.Collections.Generic,以便在_ViewImports所在的文件夹及其子文件夹中的所有Razor视图中直接使用ViewImprots文件中引用的命名空间下的类而不必添加@using语句。所以我们在这里添加 tag helper之后,所有的视图中都可以使用它。

我们仍然需要 Index action的视图,接下来在Views下创建Home文件夹,然后创建Index.cshtml视图文件,文件内容如下:

  1. <h1>Home</h1>
  2. @if (User.Identity.IsAuthenticated)
  3. {
  4. <p>User @User.Identity.Name is signed in. <a href="/Account/Logout">Logout</a> </p>
  5. }
  6. else
  7. {
  8. <p>No user is signed in.</p>
  9. }
  10. <div>
  11. <a href="/Account/Login">Login</a>
  12. </div>
  13. <div>
  14. <a href="/Account/Register">Register</a>
  15. </div>
  16. <div>
  17. <a href="/Account/ForgotPassword">Forgot Password</a>
  18. </div>

运行该项目,你应该看到一个非常简单的页面,提醒你没有用户登录。里面里的链接也无法正常工作,因为我们稍后才会处理它们。

译者注:在vs code 中按下快捷键 Ctrl+~ 来启动终端,

键入 dotnet run即可运行项目,如果不行的话先运行dotnet build

ASP.NET Core Identity

ASP.NET Core Identity是ASP.NET Core的成员系统,它提供了管理用户帐户所需的功能。通过使用它,我们将能够创建用户并生成token以用于电子邮件确认和密码重置。

你需要添加下面两个Nuget包:

  1. Microsoft.AspNetCore.Identity
  2. 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包:

  1. Microsoft.EntityFrameworkCore.Sqlite

然后打开项目的.csproj文件,在 ItemGroup中添加(译者注:这里不能忽略,这里添加了Ef的命令行工具):

  1. Microsoft.EntityFrameworkCore.Tools.DotNet
  2. Microsoft.EntityFrameworkCore.Tools

最终的代码看起来是这样的:

  1. ...
  2. <ItemGroup>
  3. <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
  4. <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
  5. </ItemGroup>
  6. <ItemGroup>
  7. <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
  8. </ItemGroup>
  9. ...

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配置:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddMvc();
  4. services.AddDbContext<IdentityDbContext>(options =>
  5. {
  6. options.UseSqlite(
  7. "Data Source=users.sqlite",
  8. builder =>
  9. {
  10. builder.MigrationsAssembly("AspNetIdentityFromScratch");
  11. });
  12. });
  13. services.AddIdentity<IdentityUser, IdentityRole>()
  14. .AddEntityFrameworkStores<IdentityDbContext>()
  15. .AddDefaultTokenProviders();
  16. // services.AddTransient<IEmailSender, EmailSender>();
  17. }

我们向Ioc容器中注册了 IdentityDbContext 并使用Data Source = users.sqlite 作为连接字符串。当我们创建数据库时,会在项目的输出目录中创建一个名为users.sqlite的文件(稍后会详细介绍)。

请注意UseSqlite扩展方法中使用的第二个参数:

  1. builder =>
  2. {
  3. builder.MigrationsAssembly("IdentityFromScratch");
  4. });

这段代码是必需的,因为当我们运行工具(dotnet ef)来创建数据库迁移时,将执行检查来验证IdentityDbContext类是否与你运行工具的项目位于相同的程序集中。如果不是这样,你可能会得到一个错误:

  1. Your target project 'IdentityFromScratch' doesn't match your migrations assembly 'Microsoft.AspNetCore.Identity.EntityFrameworkCore'
  2. ...

这就是为什么我们需要使用UseSqlite的第二个参数。 IdentityFromScratch是我用于项目的名称,你需要将其更新为你用于你项目的名称。

接下来是注册Identity:

  1. services.AddIdentity<IdentityUser, IdentityRole>()
  2. .AddEntityFrameworkStores<IdentityDbContext>()
  3. .AddDefaultTokenProviders();

这里我们指定了我们想要用于用户的类IdentityUser和角色IdentityRole。如前所述,你可以指定你自己的IdentityUser的子类,但是如果你这样做,还需要更改DbContext的注册方式,例如:

  1. services.AddDbContext<IdentityDbContext<YourCustomUserClass>>(...
  2. services.AddIdentity<YourCustomUserClass, IdentityRole>()
  3. .AddEntityFrameworkStores<IdentityDbContext<YourCustomUserClass>>(...

你可能注意到我没有提到关于IdentityRole的任何事情。你可以忽略这个,因为你可以添加角色claim来取代role。这里指定IdentityRole我猜这只是因为在旧成员系统中role的概念非常突出,不过在我们普遍使用claim的今天,role已经不那么重要了。另外,指定一个自定义的IdentityRole会使事情变得更加复杂,我将向你展示如何使用claim将role添加到你的用户。

接下来:

  1. .AddEntityFrameworkStores<IdentityDbContext>()

这仅仅指定使用哪个DbContext,最后:

  1. .AddDefaultTokenProviders();

TokenProvider是生成确认电子邮件和重置密码token的组件,稍后我们将用到它们。

现在我们只需要更新管道:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  2. {
  3. //...
  4. app.UseIdentity();
  5. app.UseStaticFiles();
  6. app.UseMvcWithDefaultRoute();
  7. }

使用UseIdentity扩展方法时会发生什么呢?这实际上它会设置cookie中间件,cookie中间件做的是当MVC中的controller action返回401响应时将用户重定向到登录页面,并且在用户登录并创建身份验证cookie后,将其转换为ClaimsPrincipal和ClaimsIdentity,也就是在你使用Request.User时获取到的实际对象。

生成存储用户数据的数据库

在使用新版本的EF时,生成数据库基本上只能使用migrations。接下来我们需要创建一个迁移,然后让工具生成数据库。

打开vscode中的终端(上文中有提到过),输入下面的命令:

  1. dotnet ef migrations add Initial

这将创建名为“Initial”的迁移,使用下面的命令应用它:

  1. dotnet ef database update

该命令所做的是应用所有pending的迁移,在本例情况下,它只会是“Initial”,它将创建ASP.NET Core Identity所必需的表。

现在应该在输出文件夹中有一个名为users.sqlite的文件。

你当然也可以以编程方式生成数据库(但你始终需要首先创建迁移)。

这样,如果你想与其他人分享你的项目,他们将不必运行dotnet ef database update 就能运行该项目。

为实现此目标,在Startup.cs的配置方法中添加:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IdentityDbContext dbContext)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. dbContext.Database.Migrate(); // 如果数据库不存在则会被创建
  7. }
  8. //...
  9. }

不过你一定要在设置为“development”的环境中运行。通常情况下,如果你从Visual Studio运行那么就是development,但是如果你决定从命令行运行的话:

  1. dotnet run --environment "Development"

发送确认邮件

为了启用电子邮件验证和密码重置,我们要让应用程序能够发送电子邮件。当你正处在开发阶段时,最简单的方法就是把邮件信息存到磁盘文件上。这样可以节省你在尝试操作时不得不等待电子邮件的时间。首先我们需要做的是创建一个接口,我们将其命名为IMessageService

  1. public interface IMessageService
  2. {
  3. Task Send(string email, string subject, string message);
  4. }

写邮件到文件的实现类:

  1. public class FileMessageService : IMessageService
  2. {
  3. Task IMessageService.Send(string email, string subject, string message)
  4. {
  5. var emailMessage = $"To: {email}\nSubject: {subject}\nMessage: {message}\n\n";
  6. File.AppendAllText("emails.txt", emailMessage);
  7. return Task.FromResult(0);
  8. }
  9. }

最后一件事是在Startup.cs的ConfigureServices中注册这个服务:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. //...
  4. services.AddTransient<IMessageService, FileMessageService>();
  5. }

用户注册页面

对于这个演示,我们将构建最简单的Razor视图,因为我们的目的是展示如何使用ASP.NET Core Identity。

在Controllers文件夹中创建AccountController。

在构造函数中添加UserManager 、SignInManager (这些依赖项在Startup.cs中的ConfigureServices中添加services.AddIdentity <...>时被注册)和IMessageService:

  1. public class AccountController : Controller
  2. {
  3. private readonly UserManager<IdentityUser> _userManager;
  4. private readonly SignInManager<IdentityUser> _signInManager;
  5. private readonly IMessageService _messageService;
  6. public AccountController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IMessageService messageService)
  7. {
  8. this._userManager = userManager;
  9. this._signInManager = signInManager;
  10. this._messageService = messageService;
  11. }
  12. //...

UserManager是用来创建用户和生成验证token的类。SignInManager 提供密码验证的功能,以及用户的登录与注销(通过处理authentication cookie)。

创建注册方法:

  1. public IActionResult Register()
  2. {
  3. return View();
  4. }

以及 Views/Account/Register.cshtml 文件:

  1. <form method="POST">
  2. <div>
  3. <label >Email</label>
  4. <input type="email" name="email"/>
  5. </div>
  6. <div>
  7. <label>Password</label>
  8. <input type="password" name="password"/>
  9. </div>
  10. <div>
  11. <label>Retype password</label>
  12. <input type="password" name="repassword"/>
  13. </div>
  14. <input type="submit"/>
  15. </form>
  16. <div asp-validation-summary="All"></div>

值得注意的是 asp-validation-summary tag helper,tag helper是ASP.NET Core中的新功能。它们用来替代旧的HtmlHelpers。

validation summary tag helper,设置的值是All,所以它将显示所有模型错误。我们将使用它来显示创建用户时检测到的任何错误(例如,用户名已被占用或密码不匹配)。

下面是用户注册表单接收的实际处理方法:

  1. [HttpPost]
  2. public async Task<IActionResult> Register(string email, string password, string repassword)
  3. {
  4. if (password != repassword)
  5. {
  6. ModelState.AddModelError(string.Empty, "两次输入的密码不一致");
  7. return View();
  8. }
  9. var newUser = new IdentityUser
  10. {
  11. UserName = email,
  12. Email = email
  13. };
  14. var userCreationResult = await _userManager.CreateAsync(newUser, password);
  15. if (!userCreationResult.Succeeded)
  16. {
  17. foreach(var error in userCreationResult.Errors)
  18. ModelState.AddModelError(string.Empty, error.Description);
  19. return View();
  20. }
  21. var emailConfirmationToken = await _userManager.GenerateEmailConfirmationTokenAsync(newUser);
  22. var tokenVerificationUrl = Url.Action("VerifyEmail", "Account", new {id = newUser.Id, token = emailConfirmationToken}, Request.Scheme);
  23. await _messageService.Send(email, "验证电子邮件", $"点 <a href=\"{tokenVerificationUrl}\">我</a> 验证邮件");
  24. return Content("请检查你的邮箱,我们向你发送了邮件。");
  25. }

要创建我们的新用户,我们首先必须创建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”角色添加到用户的示例:

  1. await _userManager.AddClaimAsync(identityUser, new Claim(ClaimTypes.Role, "Administrator"));

然后,你可以像以前一样在controller action上要求“Administrator”角色:

  1. [Authorize(Roles="Administrator")]
  2. public IActionResult RequiresAdmin()
  3. {
  4. return Content("OK");
  5. }

邮件验证

让我们来创建controller action,当用户点击电子邮件中的链接时将会调用该操作:

  1. public async Task<IActionResult> VerifyEmail(string id, string token)
  2. {
  3. var user = await _userManager.FindByIdAsync(id);
  4. if(user == null)
  5. throw new InvalidOperationException();
  6. var emailConfirmationResult = await _userManager.ConfirmEmailAsync(user, token);
  7. if (!emailConfirmationResult.Succeeded)
  8. return Content(emailConfirmationResult.Errors.Select(error => error.Description).Aggregate((allErrors, error) => allErrors += ", " + error));
  9. return Content("Email confirmed, you can now log in");
  10. }

我们需要使用用户的ID来加载IdentityUser。有了这个和电子邮件确认token,我们可以调用userManagerConfirmEmailAsync方法。如果token是正确的,则用户数据库表中的EmailConfirmed属性将被更新以指示用户的电子邮件已经确认。

出于简化示例的目的,我们使用了Content方法。你可以创建一个新视图,然后加一个跳转到登录页面的连接。有一件事你不应该做的就是在确认之后自动登录用户。

这是因为如果用户多次点击确认链接,都不会引发任何错误。

如果你想在确认电子邮件后自动登录用户,则该链接本质上将成为登录的一种方式,而且不需要使用密码,又能多次使用。

登录页面

首先在AccountController中创建一个Login方法来处理GET请求:

  1. public IActionResult Login()
  2. {
  3. return View();
  4. }

以及它的视图 Views/Account/Login.cstml:

  1. <form method="POST">
  2. <div>
  3. <label>Email</label>
  4. <input type="email" name="email"/>
  5. </div>
  6. <div>
  7. <label>Password</label>
  8. <input type="password" name="password"/>
  9. </div>
  10. <input type="submit"/>
  11. </form>
  12. <div asp-validation-summary="All"></div>

控制器处理POST请求的操作:

  1. [HttpPost]
  2. public async Task<IActionResult> Login(string email, string password, bool rememberMe)
  3. {
  4. var user = await _userManager.FindByEmailAsync(email);
  5. if (user == null)
  6. {
  7. ModelState.AddModelError(string.Empty, "Invalid login");
  8. return View();
  9. }
  10. if (!user.EmailConfirmed)
  11. {
  12. ModelState.AddModelError(string.Empty, "Confirm your email first");
  13. return View();
  14. }
  15. var passwordSignInResult = await _signInManager.PasswordSignInAsync(user, password, isPersistent: rememberMe, lockoutOnFailure: false);
  16. if (!passwordSignInResult.Succeeded)
  17. {
  18. ModelState.AddModelError(string.Empty, "Invalid login");
  19. return View();
  20. }
  21. return Redirect("~/");
  22. }

这里我们使用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中可以执行以下操作:

  1. if (Url.IsLocalUrl(returnUrl))
  2. {
  3. return Redirect(returnUrl);
  4. }else
  5. {
  6. return Redirect("~/");
  7. }

密码重置

对于密码重置,我们需要一个页面。

首先controller action,我们命名为ForgotPassword

  1. public IActionResult ForgotPassword()
  2. {
  3. return View();
  4. }

视图 /Views/Account/ForgotPassword.cshtml:

  1. <form method="POST">
  2. <div>
  3. <label>Email</label>
  4. <input type="email" name="email"/>
  5. </div>
  6. <input type="submit"/>
  7. </form>

处理Post请求的方法:

  1. [HttpPost]
  2. public async Task<IActionResult> ForgotPassword(string email)
  3. {
  4. var user = await _userManager.FindByEmailAsync(email);
  5. if (user == null)
  6. return Content("Check your email for a password reset link");
  7. var passwordResetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
  8. var passwordResetUrl = Url.Action("ResetPassword", "Account", new {id = user.Id, token = passwordResetToken}, Request.Scheme);
  9. await _messageService.Send(email, "Password reset", $"Click <a href=\"" + passwordResetUrl + "\">here</a> to reset your password");
  10. return Content("Check your email for a password reset link");
  11. }

你可能会注意到,无论电子邮件是否属于一个存在的用户,用户都将看到相同的消息。

你应该这样做,因为如果你不这样做,则可以使用此功能来发现用户是否在你的站点中拥有帐户。

控制器的其余部分只是生成token并发送电子邮件。

我们仍然需要构建ResetPasswordcontroller action来处理我们用电子邮件发送给用户的链接。

  1. [HttpPost]
  2. public async Task<IActionResult> ResetPassword(string id, string token, string password, string repassword)
  3. {
  4. var user = await _userManager.FindByIdAsync(id);
  5. if (user == null)
  6. throw new InvalidOperationException();
  7. if (password != repassword)
  8. {
  9. ModelState.AddModelError(string.Empty, "Passwords do not match");
  10. return View();
  11. }
  12. var resetPasswordResult = await _userManager.ResetPasswordAsync(user, token, password);
  13. if (!resetPasswordResult.Succeeded)
  14. {
  15. foreach(var error in resetPasswordResult.Errors)
  16. ModelState.AddModelError(string.Empty, error.Description);
  17. return View();
  18. }
  19. return Content("Password updated");
  20. }

如果你想知道为什么我没有在表单中添加token和用户ID作为隐藏字段,这是因为MVC model binder会在url参数中找到它们。不过大多数示例(以及来自Visual Studio的个人用户帐户的默认模板)都将其添加为隐藏字段。

这个action没有太多内容,只是使用_userManager.ResetPasswordAsync为用户设置一个新的密码。

注销登陆

注销一个用户只需要使用SignInManager并调用SignOutAsync

  1. [HttpPost]
  2. public async Task<IActionResult> Logout()
  3. {
  4. await _signInManager.SignOutAsync();
  5. return Redirect("~/");
  6. }

仅仅允许用户使用Http Post 方式注销绝对是个更好的方法。

请注意,为了简单起见,我在示例中没有这样做。所以如果你照着做的话请不要添加[HttpPost](Index页面有链接到/Account/Logout),或者将Index.cshtml中的注销链接更改为发送到/Account/Logout的表单。

配置

你可能想知道,如果我选择与AccountController不同的名称,所有这些代码还能工作吗?也许你会将登录action命名为SignIn而不是Login。

如果你这样做是行不通的。例如,如果你在controller action中使用[Authorize]属性,并且用户导航到该操作的URL,则重定向将发送到 /Account/Login。

那么你怎么能改变设置呢?答案就是在Startup.cs的ConfigureServices中注册Identity服务的时候指定配置。

以下是如何更改默认的登录页面:

  1. services.ConfigureApplicationCookie(o =>
  2. {
  3. o.LoginPath = "/Account/SignIn";
  4. });

设置密码要求,例如:

  1. services.AddIdentity<IdentityUser, IdentityRole>(
  2. options =>
  3. {
  4. options.Password.RequireDigit = false;
  5. options.Password.RequiredLength = 6;
  6. options.Password.RequireLowercase = false;
  7. options.Password.RequireNonAlphanumeric = false;
  8. options.Password.RequireUppercase = false;
  9. }
  10. )

这将设置密码限制为最少6个字符。

你还可以要求确认的电子邮件:

  1. services.Configure<IdentityOptions>(options => {
  2. options.SignIn.RequireConfirmedEmail = true;
  3. });

如果这只了要求电子邮件确认,那么你将不必检查IdentityUserEmailConfirmed属性。当你尝试使用SignInManager对用户进行登录时,会登录失败,并且结果将包含名为IsNotAllowed的属性,而且它的值为true

还有其他配置选项可供使用,例如帐户被锁定之前允许的尝试次数以及被锁定多长时间。

使用 SendGrid 真正的发送邮件

如果你真的想发送电子邮件,你可以使用SendGrid。这是一个电子邮件服务,他们有一个免费的Plan,所以你可以试试看。

你需要安装它的nuget包(译者注:这个也是不能忽略的):

  1. SendGrid.NetCore

以下是使用SendGrid的IMessageService的实现:

  1. public class SendGridMessageService : IMessageService
  2. {
  3. public async Task Send(string email, string subject, string message)
  4. {
  5. var emailMessage = new SendGrid.SendGridMessage();
  6. emailMessage.AddTo(email);
  7. emailMessage.Subject = subject;
  8. emailMessage.From = new System.Net.Mail.MailAddress("senderEmailAddressHere@senderDomainHere", "info");
  9. emailMessage.Html = message;
  10. emailMessage.Text = message;
  11. var transportWeb = new SendGrid.Web("PUT YOUR SENDGRID KEY HERE");
  12. try{
  13. await transportWeb.DeliverAsync(emailMessage);
  14. }catch(InvalidApiRequestException ex){
  15. System.Diagnostics.Debug.WriteLine(ex.Errors.ToList().Aggregate((allErrors, error) => allErrors += ", " + error));
  16. }
  17. }
  18. }

然后更新ConfigureService

  1. services.AddTransient<IMessageService, SendGridMessageService>();

全文完

从0引入 ASP.NET Identity Core的更多相关文章

  1. 【译】ASP.NET Identity Core 从零开始

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

  2. [转帖]2016年时的新闻:ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0

    ASP.NET Core 1.0.ASP.NET MVC Core 1.0和Entity Framework Core 1.0 http://www.cnblogs.com/webapi/p/5673 ...

  3. ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0

    ASP.NET 5.0 将改名为 ASP.NET Core 1.0 ASP.NET MVC 6  将改名为 ASP.NET MVC Core 1.0 Entity Framework 7.0    将 ...

  4. ASP.net core 2.0.0 中 asp.net identity 2.0.0 的基本使用(一)—修改数据库连接

    开发环境:vs2017  版本:15.3.5 项目环境:.net framework 4.6.1    模板asp.net core 2.0  Web应用程序(模型视图控制器) 身份验证:个人用户账号 ...

  5. ASP.net core 2.0.0 中 asp.net identity 2.0.0 的基本使用(四)—用户注册

    修改用户注册 1.修改用户名注册规则. 打开Controllers目录下的AccountController.cs. 在控制器中找到 public async Task<IActionResul ...

  6. ASP.net core 2.0.0 中 asp.net identity 2.0.0 的基本使用(三)—用户账户及cookie配置

    修改用户账户及cookie配置 一.修改密码强度和用户邮箱验证规则: 打开Startup.cs,找到public void ConfigureServices(IServiceCollection s ...

  7. ASP.net core 2.0.0 中 asp.net identity 2.0.0 的基本使用(二)—启用用户管理

    修改和启用默认的用户账户管理和角色管理 一.修改Models目录中的ApplicationUser.cs类文件,如下 namespace xxxx.Models{    //将应用程序用户的属性添加到 ...

  8. ASP.NET Identity V2

    Microsoft.AspNet.Identity是微软在MVC 5.0中新引入的一种membership框架,和之前ASP.NET传统的membership以及WebPage所带来的SimpleMe ...

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

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

随机推荐

  1. Oracle.ManagedDataAccess.dll 连接Oracle数据库不需要安装客户端

    最开始,连接Oracle 数据是需要安装客户端的,ado.net 后来由于微软未来不再支持 System.Data.OracleClient 这个 Data Provider 的研发,从 .NET 4 ...

  2. C#操作Excel(读取)

    一.使用OleDb,这个法子好像不大好使.容易读错.引用System.Data.OleDb;     /**//// <summary>        /// 返回Excel数据源     ...

  3. 通过hibernate封装数据库持久化过程回顾泛型/继承/实现等概念

    前言 在开发过程中,我们不难发现,客户的需求以及产品的定位对开发内容的走向有很大的决策作用,而这些往往需要在一开始就尽可能考虑周全和设计完善.为什么说是尽可能,因为我们都知道,需求这种东西,一言难尽. ...

  4. 自己动手修改Robotium代码(上)

    Robotium作为Android自动化测试框架,还有许多不完善的地方,也不能满足测试人员的所有要求.那么,本文以四个实际中碰到的问题为例,介绍改动Robotium源码的过程. public bool ...

  5. angular2.x指令

    1.指令 *ngIf: 判断 isActive 为true时 <user-detail> 生效展示 <user-detail *ngIf="isActive"&g ...

  6. Python案例分享

    1.过桥(爬金字塔): 1 i = 1 2 while i <= 9: 3 if i < 6: 4 j = 0 5 while j < i: 6 print('*',end=' ') ...

  7. python并发编程之多进程一

    一,什么是进程 进程是操作系统结构的基础:是一个正在执行的程序:计算机中正在运行的程序实例:可以分配给处理器并由处理器执行的一个实体: 二,进程与程序的区别 进程即运行中的程序,从中即可知,进程是在运 ...

  8. VUE 与其他常见前端框架对比

    对比其他框架(转官方文档) 这个页面无疑是最难编写的,但我们认为它也是非常重要的.或许你曾遇到了一些问题并且已经用其他的框架解决了.你来这里的目的是看看 Vue 是否有更好的解决方案.这也是我们在此想 ...

  9. 51Nod 2006 飞行员配对(二分图最大匹配)

    链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=2006 思路: 二分匹配 注意n m的关系 代码: #include ...

  10. form表单提交引发的血案

    最近,公司某条产品线上的一个功能出了问题:点击查询的时候,该页面在IE上直接卡死,chrome上会卡顿一段时间候提交表单进行查询.拿到这个bug单子以后,简单重现了下,基本上定位到是查询操作中的问题, ...