ASP.NET Core Blazor 中的帐户确认和密码恢复
注意
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
本文介绍如何为 ASP.NET Core Blazor Web App 配置电子邮件确认和密码恢复。
注意
本文仅适用于 Blazor Web Apps。 若要为具有 ASP.NET Core 的独立Blazor WebAssembly应用实现电子邮件确认和密码恢复,请参阅 ASP.NET Core ASP.NET Core Blazor WebAssembly Identity中的帐户确认和密码恢复。Identity
命名空间
本文中示例使用的应用命名空间为 BlazorSample
。 更新这些代码示例以使用你的应用的命名空间。
选择并配置电子邮件提供程序
本文中使用 Mailchimp 的事务 API 通过 Mandrill.net 发送电子邮件。 建议使用电子邮件服务(而不是 SMTP)来发送电子邮件。 SMTP 难以正确配置和保护。 无论使用哪种电子邮件服务,都请访问其 .NET 应用指南、创建帐户、为其服务配置 API 密钥,并安装所需的任何 NuGet 包。
创建用于保存机密电子邮件提供程序 API 密钥的类。 本文中的示例使用一个名为EmailAuthKey
属性的AuthMessageSenderOptions
类来保存键。
AuthMessageSenderOptions.cs
:
namespace BlazorSample;
public class AuthMessageSenderOptions
{
public string? EmailAuthKey { get; set; }
}
在 Program
文件中注册 AuthMessageSenderOptions
配置实例:
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
为提供程序的安全密钥配置用户机密
如果项目已为机密管理器工具初始化,则项目文件()中已有应用机密标识符(<AppSecretsId>
.csproj
)。 在 Visual Studio 中,可以通过在解决方案资源管理器中选择项目时查看“属性”面板来判断应用机密 ID 是否存在。 如果应用尚未初始化,请在打开项目的目录的命令行界面中执行以下命令。 在 Visual Studio 中,可以使用开发人员 PowerShell 命令提示符。
dotnet user-secrets init
使用机密管理器工具设置 API 密钥。 在下面的示例中,键名称是 EmailAuthKey
匹配 AuthMessageSenderOptions.EmailAuthKey
的,密钥由 {KEY}
占位符表示。 使用 API 密钥执行以下命令:
dotnet user-secrets set "EmailAuthKey" "{KEY}"
如果使用 Visual Studio,可以通过右键单击解决方案资源管理器中的服务器项目并选择“管理用户机密”来确认是否已设置机密。
有关详细信息,请参阅在 ASP.NET Core 开发中安全存储应用机密。
警告
不要在客户端代码中存储应用机密、连接字符串、凭据、密码、个人标识号(PIN)、专用 C#/.NET 代码或私钥/令牌,这始终不安全。 在测试/暂存和生产环境中,服务器端 Blazor 代码和 Web API 应使用安全身份验证流,以避免在项目代码或配置文件中维护凭据。 在本地开发测试之外,我们建议避免使用环境变量来存储敏感数据,因为环境变量不是最安全的方法。 对于本地开发测试, 建议使用机密管理器工具 来保护敏感数据。 有关详细信息,请参阅 安全维护敏感数据和凭据。
实现 IEmailSender
以下示例基于 Mailchimp 的事务 API,该 API 使用 Mandrill.net。 对于其他提供商,请参阅有关如何实现发送电子邮件的文档。
将 Mandrill.net NuGet 包添加到项目。
添加以下 EmailSender
类以实现 IEmailSender<TUser>。 在下面的示例中, ApplicationUser
是一个 IdentityUser。 可以进一步自定义消息 HTML 标记。 只要 message
传递以 MandrillMessage
字符开头 <
,Mandrill.net API 就假定消息正文以 HTML 形式编写。
Components/Account/EmailSender.cs
:
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Mandrill;
using Mandrill.Model;
using BlazorSample.Data;
namespace BlazorSample.Components.Account;
public class EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
ILogger<EmailSender> logger) : IEmailSender<ApplicationUser>
{
private readonly ILogger logger = logger;
public AuthMessageSenderOptions Options { get; } = optionsAccessor.Value;
public Task SendConfirmationLinkAsync(AppUser user, string email,
string confirmationLink) => SendEmailAsync(email, "Confirm your email",
"<html lang=\"en\"><head></head><body>Please confirm your account by " +
$"<a href='{confirmationLink}'>clicking here</a>.</body></html>");
public Task SendPasswordResetLinkAsync(AppUser user, string email,
string resetLink) => SendEmailAsync(email, "Reset your password",
"<html lang=\"en\"><head></head><body>Please reset your password by " +
$"<a href='{resetLink}'>clicking here</a>.</body></html>");
public Task SendPasswordResetCodeAsync(AppUser user, string email,
string resetCode) => SendEmailAsync(email, "Reset your password",
"<html lang=\"en\"><head></head><body>Please reset your password " +
$"using the following code:<br>{resetCode}</body></html>");
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
if (string.IsNullOrEmpty(Options.EmailAuthKey))
{
throw new Exception("Null EmailAuthKey");
}
await Execute(Options.EmailAuthKey, subject, message, toEmail);
}
public async Task Execute(string apiKey, string subject, string message,
string toEmail)
{
var api = new MandrillApi(apiKey);
var mandrillMessage = new MandrillMessage("sarah@contoso.com", toEmail,
subject, message);
await api.Messages.SendAsync(mandrillMessage);
logger.LogInformation("Email to {EmailAddress} sent!", toEmail);
}
}
注意
邮件的正文内容可能需要电子邮件服务提供程序的特殊编码。 如果电子邮件中无法跟踪邮件正文中的链接,请参阅服务提供商的文档来解决问题。
将应用配置为支持电子邮件
在 Program
文件中,将电子邮件发件人实现更改为 EmailSender
:
- builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
+ builder.Services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>();
从应用中删除 IdentityNoOpEmailSender
(Components/Account/IdentityNoOpEmailSender.cs
)。
在 RegisterConfirmation
组件 (Components/Account/Pages/RegisterConfirmation.razor
) 中,删除 @code
块中用于检查 EmailSender
是否是 IdentityNoOpEmailSender
的条件块:
- else if (EmailSender is IdentityNoOpEmailSender)
- {
- ...
- }
此外,在 RegisterConfirmation
组件中,删除用于检查 emailConfirmationLink
字段的 Razor 标记和代码,只留下指示用户检查其电子邮件的行。
- @if (emailConfirmationLink is not null)
- {
- ...
- }
- else
- {
<p>Please check your email to confirm your account.</p>
- }
@code {
- private string? emailConfirmationLink;
...
}
在站点具有用户后启用帐户确认
在具有用户的站点上启用帐户确认会锁定所有现有用户。 现有用户被锁定,因为未确认其帐户。 若要解决现有用户锁定问题,请使用以下方法之一:
- 更新数据库以将所有现有用户标记为已经过确认。
- 确认现有用户。 例如,批量发送包含确认链接的电子邮件。
电子邮件和活动超时
默认的非活动超时为 14 天。 下面的代码将非活动超时设置为 5 天,并启用可调过期:
builder.Services.ConfigureApplicationCookie(options => {
options.ExpireTimeSpan = TimeSpan.FromDays(5);
options.SlidingExpiration = true;
});
更改所有 ASP.NET Core 数据保护令牌的使用期限
以下代码将数据保护令牌超时期限更改为 3 小时:
builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
options.TokenLifespan = TimeSpan.FromHours(3));
内置 Identity 用户令牌(AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs)超时一 天。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
更改电子邮件令牌的使用期限
Identity 用户令牌的默认令牌有效期是 1 天。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
若要更改电子邮件令牌有效期,请添加自定义 DataProtectorTokenProvider<TUser> 和 DataProtectionTokenProviderOptions。
CustomTokenProvider.cs
:
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
namespace BlazorSample;
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 class CustomPasswordResetTokenProvider<TUser>
: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomPasswordResetTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<PasswordResetTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class PasswordResetTokenProviderOptions :
DataProtectionTokenProviderOptions
{
public PasswordResetTokenProviderOptions()
{
Name = "PasswordResetDataProtectorTokenProvider";
TokenLifespan = TimeSpan.FromHours(3);
}
}
在 Program
文件中将服务配置为使用自定义令牌提供程序:
builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Tokens.ProviderMap.Add("CustomEmailConfirmation",
new TokenProviderDescriptor(
typeof(CustomEmailConfirmationTokenProvider<ApplicationUser>)));
options.Tokens.EmailConfirmationTokenProvider =
"CustomEmailConfirmation";
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services
.AddTransient<CustomEmailConfirmationTokenProvider<ApplicationUser>>();
疑难解答
如果无法使用电子邮件:
- 在
EmailSender.Execute
中设置断点,以验证是否调用SendEmailAsync
。 - 使用类似于
EmailSender.Execute
的代码创建控制台应用来发送电子邮件,从而调试问题。 - 查看电子邮件提供程序网站上的帐户电子邮件历史记录页。
- 检查垃圾邮件文件夹中是否有邮件。
- 尝试使用其他电子邮件提供程序(例如 Microsoft、Yahoo 或 Gmail)上的其他电子邮件别名。
- 尝试发送到不同的电子邮件帐户。