다음을 통해 공유


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

참고 항목

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

이 문서에서는 전자 메일 확인 및 암호 복구를 사용하여 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: 로컬로 작업할 때 사용할 Development 환경을 포함하여 모든 환경에서 사용할 수 있도록 비밀을 키 자격 증명 모음에 저장할 수 있습니다. 일부 개발자는 스테이징 및 프로덕션 배포에 키 볼트를 사용하고 로컬 개발에는 Secret Manager 도구를 사용하는 것을 선호합니다.

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

비밀 관리자 도구

프로젝트가 Secret Manager 도구에 대해 이미 초기화된 경우 프로젝트 파일()에 이미 사용자 비밀 식별자(<UserSecretsId>.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에서 개발 중인 앱 비밀 보안 스토리지를 참조하세요.

경고

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

Azure Key Vault (애저 키 볼트)

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

키 자격 증명 모음을 만들고 비밀을 설정하는 방법에 대해서는, Azure Key Vault 시작을 위한 리소스를 연결하는 문서인 Azure Key Vault 비밀 정보(Azure 설명서) 을 참조하세요. 이 섹션의 예제에서 비밀 이름은 "EmailAuthKey"입니다.

Entra 포털 또는 Azure 포털에서 키 자격 모음을 구성할 때:

  • Azure 역할 기반 액세스 제어(RABC)를 사용하도록 키 자격 증명 모음을 구성합니다. 로컬 개발 및 테스트를 포함하여 Azure Virtual Network에서 작동하지 않는 경우 네트워킹 단계에서 공용 액세스가 사용하도록 설정되어 있는지 확인합니다(선택됨). 공용 액세스를 사용하도록 설정하면 키 자격 증명 모음 엔드포인트만 노출됩니다. 인증된 계정은 여전히 액세스에 필요합니다.

  • Azure 관리 Identity를 생성하거나, 사용할 계획이 있는 기존 관리 Identity 형에 Key Vault 비밀 사용자 역할을 추가합니다. 배포를 호스팅하는 Azure App Service에 Managed Identity 를 할당합니다.사용자가 할당한>Identity>>추가입니다.

    참고 항목

    Azure CLI 또는 Visual Studio의 Azure 서비스 인증을 사용하여 키 자격 증명 모음 액세스를 위해 권한 있는 사용자와 함께 로컬로 앱을 실행하려는 경우 Key Vault 비밀 사용자 역할을 사용하여 IAM(Access Control)에 개발자 Azure 사용자 계정을 추가합니다. Visual Studio를 통해 Azure CLI를 사용하려면 개발자 PowerShell 패널에서 명령을 실행하고 az login 프롬프트에 따라 테넌트에 인증합니다.

이 섹션의 코드를 구현하려면 키 자격 증명 모음 및 비밀을 만들 때 Azure에서 키 자격 증명 모음 URI(예: "https://contoso.vault.azure.net/", 후행 슬래시 필요) 및 비밀 이름(예: "EmailAuthKey")을 기록합니다.

중요합니다

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

서버 프로젝트에 다음 AzureHelper 클래스를 추가합니다. GetKeyVaultSecret 메서드는 키 볼트에서 비밀을 검색합니다. 네임스페이스(BlazorSample.Helpers)를 프로젝트 네임스페이스 체계와 일치하도록 조정합니다.

Helpers/AzureHelper.cs:

using Azure.Core;
using Azure.Security.KeyVault.Secrets;

namespace BlazorSample.Helpers;

public static class AzureHelper
{
    public static string GetKeyVaultSecret(string vaultUri, 
        TokenCredential credential, string secretName)
    {
        var client = new SecretClient(new Uri(vaultUri), credential);
        var secret = client.GetSecretAsync(secretName).Result;

        return secret.Value.Value;
    }
}

서버 프로젝트의 Program 파일에 서비스가 등록된 경우 옵션 구성비밀을 가져오고 바인딩합니다.

TokenCredential? credential;

if (builder.Environment.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

var emailAuthKey = AzureHelper.GetKeyVaultSecret("{VAULT URI}", credential, 
    "EmailAuthKey");

var authMessageSenderOptions = 
    new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
    .Bind(authMessageSenderOptions);

참고 항목

비-Production환경에서는 인증을 간소화하기 위해 Azure 호스팅 환경에서 사용되는 자격 증명과 로컬 개발에 사용되는 자격 증명을 결합하여 Azure에 배포하는 앱을 개발하는 동안 이전 예제에서는 DefaultAzureCredential을 사용합니다. 자세한 내용은 시스템 할당 관리 ID를 사용하여 Azure 리소스에 Azure 호스팅 .NET 앱 인증을 참조하세요.

앞의 예제는 관리 Identity 클라이언트 ID({MANAGED IDENTITY CLIENT ID}), 디렉터리(테넌트) ID({TENANT ID}) 및 키 자격 증명 모음 URI({VAULT URI}, 예: https://contoso.vault.azure.net/, 후행 슬래시 필요)가 하드 코딩된 값에 의해 제공됨을 의미합니다. 이러한 값의 전부 또는 전부는 앱 설정 구성에서 제공할 수 있습니다. 예를 들어 다음은 앱 설정 파일의 AzureAd 노드에서 보험금고 URI를 가져오고, vaultUri은 이전 예제에서 GetKeyVaultSecret 호출에 사용할 수 있습니다.

var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;

자세한 내용은 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(ApplicationUser 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(ApplicationUser 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(ApplicationUser 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과 같은 다른 전자 메일 공급자에서 다른 전자 메일 별칭을 사용해 보세요.
  • 다른 이메일 계정으로 전송해 보세요.

추가 리소스