다음을 통해 공유


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

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

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

참고 항목

이 문서는 s에 Blazor Web App만 적용됩니다. ASP.NET Core를 사용하여 독립 실행형 Blazor WebAssembly 앱에 대한 이메일 확인 및 암호 복구를 구현하려면 ASP.NET CoreIdentity를 사용하여 ASP.NET Core Blazor WebAssemblyIdentity에서 계정 확인 및 암호 복구를 참조하세요.

네임스페이스

이 문서의 예제에서 사용하는 앱의 네임스페이스는 다음과 같습니다 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 도구: Secret Manager 도구는 로컬 컴퓨터에 개인 데이터를 저장하며 로컬 개발 중에만 사용됩니다.
  • azure Key Vault : 로컬로 작업할 때 개발 환경을 비롯한 모든 환경에서 사용할 수 있는 키 자격 증명 모음에 비밀을 저장할 수 있습니다. 일부 개발자는 스테이징 및 프로덕션 배포에 키 볼트를 사용하고 로컬 개발에는 Secret Manager 도구를 사용하는 것을 선호합니다.

프로젝트 코드 또는 구성 파일에 비밀을 저장하지 않는 것이 좋습니다. 이 섹션의 방법 중 하나 또는 둘 다와 같은 보안 인증 흐름을 사용합니다.

비밀 관리자 도구

프로젝트가 Secret Manager 도구에 대해 이미 초기화된 경우 프로젝트 파일()에 앱 비밀 식별자(<AppSecretsId>.csproj)가 이미 있습니다. Visual Studio에서는 솔루션 탐색기 프로젝트를 선택할 때 속성 패널을 확인하여 앱 비밀 ID가 있는지 확인할 수 있습니다. 앱이 초기화되지 않은 경우 프로젝트 디렉터리에 열린 명령 셸에서 다음 명령을 실행합니다. Visual Studio에서 개발자 PowerShell 명령 프롬프트를 사용할 수 있습니다.

dotnet user-secrets init

Secret Manager 도구를 사용하여 API 키를 설정합니다. 다음 예제에서 키 이름은 EmailAuthKey 일치 AuthMessageSenderOptions.EmailAuthKey하며 키는 자리 표시자로 표시됩니다 {KEY} . API 키를 사용하여 다음 명령을 실행합니다.

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

Visual Studio를 사용하는 경우 솔루션 탐색기 서버 프로젝트를 마우스 오른쪽 단추로 클릭하고 사용자 비밀 관리를 선택하여 비밀이 설정되었는지 확인할 수 있습니다.

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

Warning

앱 비밀, 연결 문자열, 자격 증명, 암호, PIN(개인 식별 번호), 개인 C#/.NET 코드 또는 프라이빗 키/토큰을 항상 안전하지 않은 클라이언트 쪽 코드에 저장하지 마세요. 테스트/스테이징 및 프로덕션 환경에서 서버 쪽 Blazor 코드 및 웹 API는 프로젝트 코드 또는 구성 파일 내에서 자격 증명을 유지 관리하지 않는 보안 인증 흐름을 사용해야 합니다. 로컬 개발 테스트 외에는 환경 변수가 가장 안전한 방법이 아니므로 환경 변수를 사용하여 중요한 데이터를 저장하는 것을 피하는 것이 좋습니다. 로컬 개발 테스트의 경우 중요한 데이터를 보호하기 위해 Secret Manager 도구를 사용하는 것이 좋습니다. 자세한 내용은 중요한 데이터 및 자격 증명을 안전하게 유지 관리하세요.

Azure Key Vault

Azure Key Vault 앱에 앱의 클라이언트 비밀을 제공하기 위한 안전한 방법을 제공합니다.

키 자격 증명 모음을 만들고 비밀을 설정하는 방법에 대해서는, Azure Key Vault 시작을 위한 리소스를 연결하는 문서인 Azure Key Vault 비밀 정보(Azure 설명서) 을 참조하세요. 이 섹션의 코드를 구현하려면 키 자격 증명 모음과 비밀을 생성할 때 Azure에서 키 자격 증명 모음 URI와 비밀 이름을 기록하십시오. 액세스 정책 패널에서 비밀에 대한 액세스 정책을 설정할 때:

  • 비밀 가져오기 권한만 필요합니다.
  • 비밀의 주요 주체로 애플리케이션을 선택합니다.

Azure 또는 Entra 포털에서 전자 메일 공급자 키에 대해 만든 비밀에 대한 액세스 권한이 앱에 부여되었는지 확인합니다.

Important

키 자격 증명 보관소의 비밀 정보는 만료 날짜와 함께 생성됩니다. 키 볼트 비밀이 만료될 시기를 추적하고 해당 날짜가 지나기 전에 앱에 대한 새 비밀을 생성해야 합니다.

서버 프로젝트에 다음 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 파일에 서비스가 등록된 경우 옵션 구성비밀을 가져오고 바인딩합니다.

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.jsonAzureAd 섹션에서 앱의 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

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

프로젝트에 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)에서 다음 항목이 있는지 @codeEmailSender확인하는 블록의 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일입니다. 다음 코드는 슬라이딩 만료가 있는 비활성 시간 제한을 5일로 설정합니다.

builder.Services.ConfigureApplicationCookie(options => {
    options.ExpireTimeSpan = TimeSpan.FromDays(5);
    options.SlidingExpiration = true;
});

모든 ASP.NET Core Data Protection 토큰 수명 변경

다음 코드는 Data Protection 토큰의 제한 시간을 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과 같은 다른 전자 메일 공급자에서 다른 전자 메일 별칭을 사용해 보세요.
  • 다른 이메일 계정으로 전송해 보세요.

추가 리소스