作者:Rick Anderson、Ponant 和 Joe Audette
本教程介绍如何使用电子邮件确认和密码重置构建 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 用户机密
dotnet user-secrets set SendGridKey <key>
Successfully saved SendGridKey to the secret store.
在 Windows 上,机密管理器将密钥/值对存储在 secrets.json 目录中的“%APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId>”文件中。
“secrets.json”文件的内容未加密。 以下标记显示“secrets.json”文件。 已删除 SendGridKey 值。
{
  "SendGridKey": "<key removed>"
}
安装 SendGrid
本教程虽然演示的是如何通过 SendGrid 添加电子邮件通知,不过也可以使用其他电子邮件提供程序。
安装 SendGrid NuGet 包:
在包管理器控制台中输入以下命令:
Install-Package SendGrid
请参阅免费开始使用 SendGrid 以注册免费的 SendGrid 帐户。
实现 IEmailSender
使用类似于下面的代码创建 IEmailSender 以实施 Services/EmailSender.cs:
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 仅用于测试,应在生产应用中禁用自动帐户验证。
若要要求使用已确认的帐户,并防止注册时立即登录,请在已搭建基架的“DisplayConfirmAccountLink = false”文件中设置 /Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs:
// 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 用户机密
dotnet user-secrets set SendGridKey <SG.key>
Successfully saved SendGridKey = SG.keyVal to the secret store.
在 Windows 上,机密管理器将密钥/值对存储在 secrets.json 目录中的“%APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId>”文件中。
“secrets.json”文件的内容未加密。 以下标记显示“secrets.json”文件。 已删除 SendGridKey 值。
{
  "SendGridKey": "<key removed>"
}
安装 SendGrid
本教程介绍如何通过 SendGrid 添加电子邮件通知,但你可以使用 SMTP 和其他机制发送电子邮件。
安装 SendGrid NuGet 包:
在包管理器控制台中输入以下命令:
Install-Package SendGrid
请参阅免费开始使用 SendGrid 以注册免费的 SendGrid 帐户。
实现 IEmailSender
使用类似于下面的代码创建 IEmailSender 以实施 Services/EmailSender.cs:
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}");
    }
}
配置启动以支持电子邮件
在“ConfigureServices”文件的 Startup.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();
搭建 RegisterConfirmation
按照为 Identity 搭建基架中的说明操作并为 Account\RegisterConfirmation 搭建基建。
当 Account.RegisterConfirmation 已搭建基架时禁用默认帐户验证
此部分仅适用于 Account.RegisterConfirmation 已搭建基架的情况。 如果 Account.RegisterConfirmation 尚未搭建基架,则请跳过此部分。
会将用户重定向到 Account.RegisterConfirmation,用户可以从中选择一个链接来确认帐户。 默认值 Account.RegisterConfirmation 仅用于测试,应在生产应用中禁用自动帐户验证。
若要要求使用已确认的帐户,并防止注册时立即登录,请在已搭建基架的“DisplayConfirmAccountLink = false”文件中设置 /Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs:
// 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 应用,并测试帐户确认和密码恢复流。
- 运行应用并注册新用户
- 查看电子邮件中的帐户确认链接。 如果未收到电子邮件,请参阅调试电子邮件。
- 单击链接以确认你的电子邮件。
- 使用电子邮件和密码登录。
- 注销。
测试密码重置
- 如果已登录,请选择“注销”。
- 选择“登录”链接,然后选择“忘记了密码?”链接。
- 输入你注册该帐户时使用的电子邮件。
- 系统将发送一封包含密码重置链接的电子邮件。 查看你的电子邮件,然后单击链接以重置密码。 密码重置成功后,可使用电子邮件和新密码登录。
重新发送电子邮件确认
在 .NET 5 或更高版本中,选择登录页上的“重新发送电子邮件确认”链接。
更改电子邮件和活动超时
默认的非活动超时为 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 是外部身份验证提供程序:
               
              
            
这两个帐户已合并。 可使用任一帐户登录。 你可能希望用户添加本地帐户,以防其社交登录身份验证服务关闭,或者更有可能失去其社交帐户的访问权限。
在站点具有用户后启用帐户确认
在具有用户的站点上启用帐户确认会锁定所有现有用户。 现有用户被锁定,因为未确认其帐户。 若要解决现有用户锁定问题,请使用以下方法之一:
- 更新数据库以将所有现有用户标记为有待确认。
- 确认现有用户。 例如,批量发送包含确认链接的电子邮件。