ASP.NET Core 中的帐户确认和密码恢复
作者:Rick Anderson、Ponant 和 Joe Audette
本教程介绍如何使用电子邮件确认和密码重置构建 ASP.NET Core 应用。 本教程不是开篇引入话题。 你应该熟悉以下内容:
有关 Blazor 指南(其补充或取代本文中的指南),请参阅 ASP.NET Core Blazor 中的帐户确认和密码恢复。
先决条件
创建和测试使用身份验证的 Web 应用
运行以下命令,创建使用身份验证的 Web 应用。
dotnet new webapp -au Individual -o WebPWrecover
cd WebPWrecover
dotnet run
使用模拟电子邮件确认函注册用户
运行应用,然后选择“注册”链接,注册用户。 注册后,你将重定向到 /Identity/Account/RegisterConfirmation
页,其中包含用于模拟电子邮件确认的链接:
- 选择
Click here to confirm your account
链接。 - 选择“登录”链接,并使用相同的凭据登录。
- 选择
Hello YourEmail@provider.com!
链接,这会重定向到/Identity/Account/Manage/PersonalData
页面。 - 选择左侧的“个人数据”选项卡,然后选择“删除”。
显示 Click here to confirm your account
链接是因为尚未使用依赖项注入容器实施并注册 IEmailSender。 请参阅RegisterConfirmation
源。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
配置电子邮件提供程序
在本教程中,SendGrid 用于发送电子邮件。 需要 SendGrid 帐户和密钥才能发送电子邮件。 建议使用 SendGrid 或其他电子邮件服务(而不是 SMTP)来发送电子邮件。 SMTP 难以确保安全和正确设置。
SendGrid 帐户可能需要添加发件人。
创建一个类以获取安全电子邮件密钥。 在此示例中,请创建 Services/AuthMessageSenderOptions.cs
:
namespace WebPWrecover.Services;
public class AuthMessageSenderOptions
{
public string? SendGridKey { get; set; }
}
配置 SendGrid 用户机密
使用机密管理工具设置 SendGridKey
。 例如:
dotnet user-secrets set SendGridKey <key>
Successfully saved SendGridKey to the secret store.
在 Windows 上,机密管理器将密钥/值对存储在 %APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId>
目录中的“secrets.json
”文件中。
“secrets.json
”文件的内容未加密。 以下标记显示“secrets.json
”文件。 已删除 SendGridKey
值。
{
"SendGridKey": "<key removed>"
}
安装 SendGrid
本教程虽然演示的是如何通过 SendGrid 添加电子邮件通知,不过也可以使用其他电子邮件提供程序。
安装 SendGrid
NuGet 包:
在包管理器控制台中输入以下命令:
Install-Package SendGrid
请参阅免费开始使用 SendGrid 以注册免费的 SendGrid 帐户。
实现 IEmailSender
使用类似于下面的代码创建 Services/EmailSender.cs
以实施 IEmailSender
:
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
namespace WebPWrecover.Services;
public class EmailSender : IEmailSender
{
private readonly ILogger _logger;
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
ILogger<EmailSender> logger)
{
Options = optionsAccessor.Value;
_logger = logger;
}
public AuthMessageSenderOptions Options { get; } //Set with Secret Manager.
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
if (string.IsNullOrEmpty(Options.SendGridKey))
{
throw new Exception("Null SendGridKey");
}
await Execute(Options.SendGridKey, subject, message, toEmail);
}
public async Task Execute(string apiKey, string subject, string message, string toEmail)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("Joe@contoso.com", "Password Recovery"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(toEmail));
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
var response = await client.SendEmailAsync(msg);
_logger.LogInformation(response.IsSuccessStatusCode
? $"Email to {toEmail} queued successfully!"
: $"Failure Email to {toEmail}");
}
}
将应用配置为支持电子邮件
将以下代码添加到 Program.cs
文件:
- 将
EmailSender
添加为临时服务。 - 注册
AuthMessageSenderOptions
配置实例。
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
当 Account.RegisterConfirmation 已搭建基架时禁用默认帐户验证
此部分仅适用于 Account.RegisterConfirmation
已搭建基架的情况。 如果 Account.RegisterConfirmation
尚未搭建基架,则请跳过此部分。
会将用户重定向到 Account.RegisterConfirmation
,用户可以从中选择一个链接来确认帐户。 默认值 Account.RegisterConfirmation
仅用于测试,应在生产应用中禁用自动帐户验证。
若要要求使用已确认的帐户,并防止注册时立即登录,请在已搭建基架的“/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
”文件中设置 DisplayConfirmAccountLink = false
:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace WebPWrecover.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IEmailSender _sender;
public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
{
_userManager = userManager;
_sender = sender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool DisplayConfirmAccountLink { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string EmailConfirmationUrl { get; set; }
public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
{
if (email == null)
{
return RedirectToPage("/Index");
}
returnUrl = returnUrl ?? Url.Content("~/");
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound($"Unable to load user with email '{email}'.");
}
Email = email;
// Once you add a real email sender, you should remove this code that lets you confirm the account
DisplayConfirmAccountLink = false;
if (DisplayConfirmAccountLink)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
EmailConfirmationUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
}
return Page();
}
}
}
仅当 Account.RegisterConfirmation
已搭建基架时才需要执行此步骤。 未搭建基架的 RegisterConfirmation 会自动检测何时使用依赖项注入容器实施并注册的 IEmailSender。
注册、确认电子邮件并重置密码
运行 Web 应用,并测试帐户确认和密码恢复流。
- 运行应用并注册新用户
- 查看电子邮件中的帐户确认链接。 如果未收到电子邮件,请参阅调试电子邮件。
- 单击链接以确认你的电子邮件。
- 使用电子邮件和密码登录。
- 注销。
测试密码重置
- 如果已登录,请选择“注销”。
- 选择“登录”链接,然后选择“忘记了密码?”链接。
- 输入你注册该帐户时使用的电子邮件。
- 系统将发送一封包含密码重置链接的电子邮件。 查看你的电子邮件,然后单击链接以重置密码。 密码重置成功后,可使用电子邮件和新密码登录。
重新发送电子邮件确认
选择“登录”页上的“重新发送电子邮件确认”链接。
更改电子邮件和活动超时
默认的非活动超时为 14 天。 下面的代码将非活动超时设置为 5 天:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
builder.Services.ConfigureApplicationCookie(o => {
o.ExpireTimeSpan = TimeSpan.FromDays(5);
o.SlidingExpiration = true;
});
var app = builder.Build();
// Code removed for brevity
更改所有数据保护令牌的使用期限
以下代码将所有数据保护令牌超时期限更改为 3 小时:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
builder.Services.Configure<DataProtectionTokenProviderOptions>(o =>
o.TokenLifespan = TimeSpan.FromHours(3));
var app = builder.Build();
// Code removed for brevity.
内置 Identity 用户令牌(请参阅 AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs)在 1 天后超时。
更改电子邮件令牌的使用期限
Identity 用户令牌的默认令牌有效期是 1 天。 本部分介绍如何更改电子邮件令牌的使用期限。
添加自定义 DataProtectorTokenProvider<TUser> 和 DataProtectionTokenProviderOptions:
public class CustomEmailConfirmationTokenProvider<TUser>
: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomEmailConfirmationTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
public EmailConfirmationTokenProviderOptions()
{
Name = "EmailDataProtectorTokenProvider";
TokenLifespan = TimeSpan.FromHours(4);
}
}
将自定义提供程序添加到服务容器:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;
using WebPWrecover.TokenProviders;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.Tokens.ProviderMap.Add("CustomEmailConfirmation",
new TokenProviderDescriptor(
typeof(CustomEmailConfirmationTokenProvider<IdentityUser>)));
config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
}).AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddTransient<CustomEmailConfirmationTokenProvider<IdentityUser>>();
builder.Services.AddRazorPages();
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
var app = builder.Build();
// Code removed for brevity.
调试电子邮件
如果无法使用电子邮件:
- 在
EmailSender.Execute
中设置断点,以验证是否调用SendGridClient.SendEmailAsync
。 - 使用类似于
EmailSender.Execute
的代码创建控制台应用来发送电子邮件。 - 查看电子邮件活动页。
- 查看垃圾邮件文件夹。
- 尝试在 Microsoft、Yahoo、Gmail 等不同的电子邮件提供程序上使用另一个电子邮件别名
- 尝试发送到不同的电子邮件帐户。
安全最佳做法是在测试和开发中避免使用生产机密。 如果将应用发布到 Azure,请在 Azure Web 应用门户中将“SendGrid 机密”设置为“应用程序设置”。 配置系统设置为从环境变量读取密钥。
合并社交和本地登录帐户
若要完成本部分,必须首先启用外部身份验证提供程序。 请参阅 Facebook、Google 和外部提供程序身份验证。
可通过单击电子邮件链接来合并本地帐户和社交帐户。 在下面的序列中,“RickAndMSFT@gmail.com”首先创建为本地登录;但也可以先将帐户创建为社交登录,然后添加本地登录。
单击“管理”链接。 请注意,与此帐户关联的外部登录(社交登录)为 0。
单击另一个登录服务链接并接受应用请求。 下图中,Facebook 是外部身份验证提供程序:
这两个帐户已合并。 可使用任一帐户登录。 你可能希望用户添加本地帐户,以防其社交登录身份验证服务关闭,或者更有可能失去其社交帐户的访问权限。
在站点具有用户后启用帐户确认
在具有用户的站点上启用帐户确认会锁定所有现有用户。 现有用户被锁定,因为未确认其帐户。 若要解决现有用户锁定问题,请使用以下方法之一:
- 更新数据库以将所有现有用户标记为有待确认。
- 确认现有用户。 例如,批量发送包含确认链接的电子邮件。
先决条件
创建和测试使用身份验证的 Web 应用
运行以下命令,创建使用身份验证的 Web 应用。
dotnet new webapp -au Individual -uld -o WebPWrecover
cd WebPWrecover
dotnet run
运行应用,然后选择“注册”链接,注册用户。 注册后,你将重定向到 /Identity/Account/RegisterConfirmation
页,其中包含用于模拟电子邮件确认的链接:
- 选择
Click here to confirm your account
链接。 - 选择“登录”链接,并使用相同的凭据登录。
- 选择
Hello YourEmail@provider.com!
链接,这会将你重定向到/Identity/Account/Manage/PersonalData
页面。 - 选择左侧的“个人数据”选项卡,然后选择“删除”。
配置电子邮件提供程序
在本教程中,SendGrid 用于发送电子邮件。 可使用其他电子邮件提供程序。 建议使用 SendGrid 或其他电子邮件服务发送电子邮件。 SMTP 很难配置,因此不会将邮件标记为垃圾邮件。
SendGrid 帐户可能需要添加发件人。
创建一个类以获取安全电子邮件密钥。 在此示例中,请创建 Services/AuthMessageSenderOptions.cs
:
namespace WebPWrecover.Services;
public class AuthMessageSenderOptions
{
public string? SendGridKey { get; set; }
}
配置 SendGrid 用户机密
使用机密管理工具设置 SendGridKey
。 例如:
dotnet user-secrets set SendGridKey <SG.key>
Successfully saved SendGridKey = SG.keyVal to the secret store.
在 Windows 上,机密管理器将密钥/值对存储在 %APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId>
目录中的“secrets.json
”文件中。
“secrets.json
”文件的内容未加密。 以下标记显示“secrets.json
”文件。 已删除 SendGridKey
值。
{
"SendGridKey": "<key removed>"
}
安装 SendGrid
本教程介绍如何通过 SendGrid 添加电子邮件通知,但你可以使用 SMTP 和其他机制发送电子邮件。
安装 SendGrid
NuGet 包:
在包管理器控制台中输入以下命令:
Install-Package SendGrid
请参阅免费开始使用 SendGrid 以注册免费的 SendGrid 帐户。
实现 IEmailSender
使用类似于下面的代码创建 Services/EmailSender.cs
以实施 IEmailSender
:
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
namespace WebPWrecover.Services;
public class EmailSender : IEmailSender
{
private readonly ILogger _logger;
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
ILogger<EmailSender> logger)
{
Options = optionsAccessor.Value;
_logger = logger;
}
public AuthMessageSenderOptions Options { get; } //Set with Secret Manager.
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
if (string.IsNullOrEmpty(Options.SendGridKey))
{
throw new Exception("Null SendGridKey");
}
await Execute(Options.SendGridKey, subject, message, toEmail);
}
public async Task Execute(string apiKey, string subject, string message, string toEmail)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("Joe@contoso.com", "Password Recovery"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(toEmail));
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
var response = await client.SendEmailAsync(msg);
_logger.LogInformation(response.IsSuccessStatusCode
? $"Email to {toEmail} queued successfully!"
: $"Failure Email to {toEmail}");
}
}
配置启动以支持电子邮件
在“Startup.cs
”文件的 ConfigureServices
方法中添加以下代码:
- 将
EmailSender
添加为临时服务。 - 注册
AuthMessageSenderOptions
配置实例。
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
搭建 RegisterConfirmation
按照为 Identity 搭建基架中的说明操作并为 Account\RegisterConfirmation
搭建基建。
当 Account.RegisterConfirmation 已搭建基架时禁用默认帐户验证
此部分仅适用于 Account.RegisterConfirmation
已搭建基架的情况。 如果 Account.RegisterConfirmation
尚未搭建基架,则请跳过此部分。
会将用户重定向到 Account.RegisterConfirmation
,用户可以从中选择一个链接来确认帐户。 默认值 Account.RegisterConfirmation
仅用于测试,应在生产应用中禁用自动帐户验证。
若要要求使用已确认的帐户,并防止注册时立即登录,请在已搭建基架的“/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
”文件中设置 DisplayConfirmAccountLink = false
:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace WebPWrecover.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IEmailSender _sender;
public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
{
_userManager = userManager;
_sender = sender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool DisplayConfirmAccountLink { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string EmailConfirmationUrl { get; set; }
public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
{
if (email == null)
{
return RedirectToPage("/Index");
}
returnUrl = returnUrl ?? Url.Content("~/");
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound($"Unable to load user with email '{email}'.");
}
Email = email;
// Once you add a real email sender, you should remove this code that lets you confirm the account
DisplayConfirmAccountLink = false;
if (DisplayConfirmAccountLink)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
EmailConfirmationUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
}
return Page();
}
}
}
仅当 Account.RegisterConfirmation
已搭建基架时才需要执行此步骤。 未搭建基架的 RegisterConfirmation 会自动检测何时使用依赖项注入容器实施并注册的 IEmailSender。
注册、确认电子邮件并重置密码
运行 Web 应用,并测试帐户确认和密码恢复流。
- 运行应用并注册新用户
- 查看电子邮件中的帐户确认链接。 如果未收到电子邮件,请参阅调试电子邮件。
- 单击链接以确认你的电子邮件。
- 使用电子邮件和密码登录。
- 注销。
测试密码重置
- 如果已登录,请选择“注销”。
- 选择“登录”链接,然后选择“忘记了密码?”链接。
- 输入你注册该帐户时使用的电子邮件。
- 系统将发送一封包含密码重置链接的电子邮件。 查看你的电子邮件,然后单击链接以重置密码。 密码重置成功后,可使用电子邮件和新密码登录。
重新发送电子邮件确认
在 ASP.NET Core 5.0 及更高版本中,选择“登录”页上的“重新发送电子邮件确认”链接。
更改电子邮件和活动超时
默认的非活动超时为 14 天。 下面的代码将非活动超时设置为 5 天:
services.ConfigureApplicationCookie(o => {
o.ExpireTimeSpan = TimeSpan.FromDays(5);
o.SlidingExpiration = true;
});
更改所有数据保护令牌的使用期限
以下代码将所有数据保护令牌超时期限更改为 3 小时:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.Configure<DataProtectionTokenProviderOptions>(o =>
o.TokenLifespan = TimeSpan.FromHours(3));
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.AddRazorPages();
}
内置 Identity 用户令牌(请参阅 AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs)在 1 天后超时。
更改电子邮件令牌的使用期限
Identity 用户令牌的默认令牌有效期是 1 天。 本部分介绍如何更改电子邮件令牌的使用期限。
添加自定义 DataProtectorTokenProvider<TUser> 和 DataProtectionTokenProviderOptions:
public class CustomEmailConfirmationTokenProvider<TUser>
: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomEmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
public EmailConfirmationTokenProviderOptions()
{
Name = "EmailDataProtectorTokenProvider";
TokenLifespan = TimeSpan.FromHours(4);
}
}
将自定义提供程序添加到服务容器:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.Tokens.ProviderMap.Add("CustomEmailConfirmation",
new TokenProviderDescriptor(
typeof(CustomEmailConfirmationTokenProvider<IdentityUser>)));
config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
}).AddEntityFrameworkStores<ApplicationDbContext>();
services.AddTransient<CustomEmailConfirmationTokenProvider<IdentityUser>>();
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.AddRazorPages();
}
调试电子邮件
如果无法使用电子邮件:
- 在
EmailSender.Execute
中设置断点,以验证是否调用SendGridClient.SendEmailAsync
。 - 使用类似于
EmailSender.Execute
的代码创建控制台应用来发送电子邮件。 - 查看电子邮件活动页。
- 查看垃圾邮件文件夹。
- 尝试在 Microsoft、Yahoo、Gmail 等不同的电子邮件提供程序上使用另一个电子邮件别名
- 尝试发送到不同的电子邮件帐户。
安全最佳做法是在测试和开发中避免使用生产机密。 如果将应用发布到 Azure,请在 Azure Web 应用门户中将“SendGrid 机密”设置为“应用程序设置”。 配置系统设置为从环境变量读取密钥。
合并社交和本地登录帐户
若要完成本部分,必须首先启用外部身份验证提供程序。 请参阅 Facebook、Google 和外部提供程序身份验证。
可通过单击电子邮件链接来合并本地帐户和社交帐户。 在下面的序列中,“RickAndMSFT@gmail.com”首先创建为本地登录;但也可以先将帐户创建为社交登录,然后添加本地登录。
单击“管理”链接。 请注意,与此帐户关联的外部登录(社交登录)为 0。
单击另一个登录服务链接并接受应用请求。 下图中,Facebook 是外部身份验证提供程序:
这两个帐户已合并。 可使用任一帐户登录。 你可能希望用户添加本地帐户,以防其社交登录身份验证服务关闭,或者更有可能失去其社交帐户的访问权限。
在站点具有用户后启用帐户确认
在具有用户的站点上启用帐户确认会锁定所有现有用户。 现有用户被锁定,因为未确认其帐户。 若要解决现有用户锁定问题,请使用以下方法之一:
- 更新数据库以将所有现有用户标记为有待确认。
- 确认现有用户。 例如,批量发送包含确认链接的电子邮件。