ASP.NET Core 中的帳戶確認與密碼重設 Blazor
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文說明如何使用電子郵件確認與密碼重設來設定 ASP.NET Core Blazor Web App。
注意
本文僅適用於 Blazor Web Apps。 若要使用 ASP.NET Core 為獨立Blazor WebAssembly應用程式實作電子郵件確認和密碼復原,請參閱IdentityBlazor WebAssemblyIdentity中的帳戶確認和密碼復原。
Namespace
本文範例所使用的應用程式,命名空間為 BlazorSample
。 更新程式碼範例以便使用您應用程式的命名空間。
選取並設定電子郵件提供者
本文中, Mailchimp 的交易式 API 是透過 Mandrill.net 來傳送電子郵件。 我們建議使用電子郵件服務來傳送電子郵件,而不是 SMTP。 SMTP 難以正確設定及保護。 無論您使用哪一個電子郵件服務、存取其 .NET 應用程式的指導、建立帳戶、為其服務設定 API 金鑰,以及安裝所需的任何 NuGet 套件。
建立類別來保存秘密電子郵件提供者 API 金鑰。 本文中的範例會使用名為 AuthMessageSenderOptions
的 EmailAuthKey
類別搭配 屬性來保存索引鍵。
AuthMessageSenderOptions.cs
:
namespace BlazorSample;
public class AuthMessageSenderOptions
{
public string? EmailAuthKey { get; set; }
}
在 AuthMessageSenderOptions
檔案註冊 Program
設定執行個體:
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
配置電子郵件提供者的安全性金鑰密碼
從提供者接收電子郵件提供者的安全性密鑰,並在下列指引中使用。
使用下列其中一種方法或兩種方法,將秘密提供給應用程式:
- Secret Manager 工具:秘密管理員工具會將私人數據儲存在本機電腦上,而且只會在本機開發期間使用。
- Azure Key Vault:您可以將秘密儲存在密鑰保存庫中,以用於任何環境,包括在本機工作時用於開發環境。 有些開發人員偏好使用金鑰保存庫進行預備和生產部署,並使用 Secret Manager 工具 進行本機開發。
強烈建議您避免將秘密儲存在專案程式代碼或組態檔中。 使用安全驗證流程,例如本節中任一或兩種方法。
秘密管理員工具
如果項目已經針對秘密管理員工具初始化,它就會在其項目檔中已經有應用程式秘密識別碼 (<AppSecretsId>
.csproj
) 。 在 Visual Studio 中,您可以在 方案總管 中選取專案時查看 [屬性] 面板,來判斷應用程式秘密標識碼是否存在。 如果應用程式尚未初始化,請在開啟至專案目錄的命令殼層中執行下列命令。 在 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 應該使用安全驗證流程,以避免在專案程式代碼或組態檔內維護認證。 在本機開發測試之外,建議您避免使用環境變數來儲存敏感數據,因為環境變數不是最安全的方法。 針對本機開發測試, 建議使用秘密管理員工具 來保護敏感數據。 如需詳細資訊,請參閱 安全地維護敏感數據和認證。
Azure Key Vault
Azure Key Vault 為應用程式提供一種安全的方法,來提供應用程式的用戶端密碼。
若要建立密鑰保存庫並設定秘密,請參閱 關於 Azure Key Vault 秘密 (Azure 檔),這會跨鏈接資源以開始使用 Azure Key Vault。 若要在本節中實作程序代碼,請在建立密鑰保存庫和秘密時,記錄來自 Azure 的密鑰保存庫 URI 和秘密名稱。 當您在 存取原則 面板中設定密鑰的存取原則時:
- 只需要 取得 秘密許可權。
- 選取應用程式作為秘密 主體。
在 Azure 或 Entra 入口網站中確認應用程式已被授予存取您為電子郵件提供者金鑰所創建的機密的權限。
重要
金鑰保存庫的機密已設定到期日。 請務必追蹤金鑰保存庫秘密何時到期,並在該日期通過之前為應用程式建立新的秘密。
將下列 AzureHelper
類別新增至伺服器專案。
GetKeyVaultSecret
方法會從金鑰保存庫擷取秘密。 調整命名空間 (BlazorSample.Helpers
),以符合您的專案命名空間配置。
Helpers/AzureHelper.cs
:
using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
namespace BlazorSample.Helpers;
public static class AzureHelper
{
public static string GetKeyVaultSecret(string tenantId, string vaultUri, string secretName)
{
DefaultAzureCredentialOptions options = new()
{
// Specify the tenant ID to use the dev credentials when running the app locally
// in Visual Studio.
VisualStudioTenantId = tenantId,
SharedTokenCacheTenantId = tenantId
};
var client = new SecretClient(new Uri(vaultUri), new DefaultAzureCredential(options));
var secret = client.GetSecretAsync(secretName).Result;
return secret.Value.Value;
}
}
伺服器專案的 Program
檔案中註冊服務的地方,取得並綁定秘密至 Options 設定:
var tenantId = builder.Configuration.GetValue<string>("AzureAd:TenantId")!;
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
var emailAuthKey = AzureHelper.GetKeyVaultSecret(
tenantId, vaultUri, "EmailAuthKey");
var authMessageSenderOptions =
new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
.Bind(authMessageSenderOptions);
如果您想要控制上述程式代碼運作的環境,例如避免在本機執行程式碼,因為您選擇使用 Secret Manager 工具 進行本機開發,您可以將上述程式代碼包裝在檢查環境的條件語句中:
if (!context.HostingEnvironment.IsDevelopment())
{
...
}
在伺服器專案中 appsettings.json
的 [AzureAd
] 區段中,確認應用程式的 Entra ID TenantId
是否存在,如果尚未存在,請新增下列 VaultUri
組態索引鍵和值:
"VaultUri": "{VAULT URI}"
在上述範例中,{VAULT URI}
佔位元是金鑰庫 URI。 在 URI 中包含尾端斜線。
例:
"VaultUri": "https://contoso.vault.azure.net/"
組態可用來根據應用程式的環境組態檔,配置專屬的金鑰保存庫和密鑰名稱。 例如,您可以針對開發中的 appsettings.Development.json
提供不同的組態值、在暫存環境中設置 appsettings.Staging.json
,及在生產部署時設置 appsettings.Production.json
。 如需詳細資訊,請參閱 ASP.NET Core Blazor 組態。
實作 IEmailSender
下列範例是以 Mailchimp 的交易式 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
元件中,移除 Razor 標記和程式碼來檢查 emailConfirmationLink
欄位,只保留一行指示使用者檢查其電子郵件...
- @if (emailConfirmationLink is not null)
- {
- ...
- }
- else
- {
<p>Please check your email to confirm your account.</p>
- }
@code {
- private string? emailConfirmationLink;
...
}
在網站有使用者之後啟用帳戶確認
在有使用者的網站上啟用帳戶確認,會鎖定所有現有的使用者。 現有的使用者因為未確認其帳戶而遭到鎖定。 若要解決現有的使用者鎖定問題,請使用下列其中一種方法:
- 更新資料庫,將所有現有使用者標示為已確認。
- 確認現有的使用者。 例如,批次傳送具有確認連結的電子郵件。
電子郵件和活動逾時
預設無活動逾時為 14 天。 下列程式碼會將無活動逾時設定為五天,並可滑動過期:
builder.Services.ConfigureApplicationCookie(options => {
options.ExpireTimeSpan = TimeSpan.FromDays(5);
options.SlidingExpiration = true;
});
變更所有 ASP.NET Core 資料保護權杖生命週期
下列程式碼會將所有資料保護權杖逾時期間變更為三小時:
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 使用者權杖 的預設權杖生命週期為 一天。
注意
.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 等。
- 請嘗試傳送至不同的電子郵件帳戶。