ASP.NET Core의 계정 확인 및 암호 복구 Blazor

이 문서에서는 전자 메일 확인 및 암호 복구를 사용하여 ASP.NET Core Blazor Web App을 구성하는 방법을 설명합니다.

네임스페이스

이 문서의 예제에서 사용하는 앱의 네임스페이스는 다음과 같습니다 BlazorSample. 앱의 네임스페이스를 사용하도록 코드 예제를 업데이트합니다.

전자 메일 공급자 선택 및 구성

이 문서에서 Mailchimp의 트랜잭션 API는 Mandrill.net 통해 이메일을 보내는 데 사용됩니다. 메일 서비스를 사용하여 SMTP 대신 전자 메일을 보내는 것이 좋습니다. SMTP는 제대로 구성하고 보호하기가 어렵습니다. 사용하는 전자 메일 서비스 중에서 .NET 앱에 대한 지침에 액세스하고, 계정을 만들고, 해당 서비스에 대한 API 키를 구성하고, 필요한 NuGet 패키지를 설치합니다.

보안 전자 메일 API 키를 가져오는 클래스를 만듭니다. 이 문서의 예제에서는 속성을 가진 명명된 AuthMessageSenderOptions 클래스를 EmailAuthKey 사용하여 키를 보유합니다.

AuthMessageSenderOptions:

namespace BlazorSample;

public class AuthMessageSenderOptions
{
    public string? EmailAuthKey { get; set; }
}

AuthMessageSenderOptions 파일에 구성 인스턴스를 등록합니다.Program

builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

공급자의 보안 키에 대한 사용자 암호 구성

비밀 관리자 도구를 사용하여 키를 설정합니다. 다음 예제에서는 키 이름이 EmailAuthKey 고 키는 자리 표시자로 표시됩니다 {KEY} . 명령 셸에서 앱의 루트 폴더로 이동하고 API 키를 사용하여 다음 명령을 실행합니다.

dotnet user-secrets set "EmailAuthKey" "{KEY}"

자세한 내용은 ASP.NET Core에서 개발 중인 앱 비밀 보안 스토리지를 참조하세요.

구현 IEmailSender

공급자에 대해 구현 IEmailSender 합니다. 다음 예제는 Mandrill.net 사용하는 Mailchimp의 트랜잭션 API를 기반으로 합니다. 다른 공급자의 경우 메서드에서 메시지 Execute 보내기를 구현하는 방법에 대한 설명서를 참조하세요.

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(ApplicationUser user, string email, 
        string confirmationLink) => SendEmailAsync(email, "Confirm your email", 
        $"Please confirm your account by " +
        "<a href='{confirmationLink}'>clicking here</a>.");

    public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, 
        string resetLink) => SendEmailAsync(email, "Reset your password", 
        $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");

    public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, 
        string resetCode) => SendEmailAsync(email, "Reset your password", 
        $"Please reset your password using the following code: {resetCode}");

    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)에서 다음 항목인 경우 검사 블록에서 EmailSenderIdentityNoOpEmailSender조건부 블록을 @code 제거합니다.

- 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;
});

모든 데이터 보호 토큰 수명 변경

다음 코드는 모든 데이터 보호 토큰 제한 시간을 3시간으로 변경합니다.

builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
    options.TokenLifespan = TimeSpan.FromHours(3));

기본 제공 Identity 사용자 토큰(AspNetCore/src//IdentityExtensions.Core/src/TokenOptions.cs)에는 1일 제한 시간이 있습니다.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

이메일 토큰 수명 변경

사용자 토큰의 Identity 기본 토큰 수명은 1일입니다.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 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과 같은 다른 전자 메일 공급자에서 다른 전자 메일 별칭을 사용해 보세요.
  • 다른 이메일 계정으로 전송해 보세요.

Warning

테스트 및 개발에 프로덕션 비밀을 사용하지 마세요. Azure에 앱을 게시하는 경우 Azure Web App Portal에서 비밀을 애플리케이션 설정으로 설정합니다. 구성 시스템은 환경 변수에서 키를 읽도록 설정됩니다.

사이트에 사용자가 있을 때 계정 확인 사용

사용자가 있는 사이트에서 계정 확인을 활성화하면 기존 사용자가 모두 잠깁니다. 기존 사용자는 계정이 확인되지 않으므로 잠겨 있습니다. 기존 사용자 잠금을 해결하려면 다음 방법 중 하나를 사용합니다.

  • 모든 기존 사용자를 확인됨으로 표시하도록 데이터베이스를 업데이트합니다.
  • 기존 사용자를 확인합니다. 예를 들어 확인 링크가 있는 이메일을 일괄 처리로 보냅니다.

추가 리소스