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

작성자: Rick Anderson, PonantJoe Audette

이 자습서에서는 이메일 확인 및 암호 재설정을 사용하여 ASP.NET Core 앱을 빌드하는 방법을 보여 줍니다. 이 자습서는 시작 항목이 아닙니다. 다음 사항을 잘 알고 있어야 합니다.

지침은 Blazor ASP.NET Core의 계정 확인 및 암호 복구를 참조 하세요 Blazor.

필수 조건

인증을 사용하여 웹앱 만들기 및 테스트

다음 명령을 실행하여 인증을 사용하는 웹앱을 만듭니다.

dotnet new webapp -au Individual -o WebPWrecover
cd WebPWrecover
dotnet run

시뮬레이션된 전자 메일 확인을 사용하여 사용자 등록

앱을 실행하고 등록 링크를 선택한 후 사용자를 등록합니다. 등록되면 이메일 확인을 시뮬레이트하는 링크를 포함하는 /Identity/Account/RegisterConfirmation 페이지로 리디렉션됩니다.

  • Click here to confirm your account 링크를 선택합니다.
  • 로그인 링크를 선택하고 동일한 자격 증명을 사용하여 로그인합니다.
  • /Identity/Account/Manage/PersonalData 페이지로 리디렉션되는 Hello YourEmail@provider.com! 링크를 선택합니다.
  • 왼쪽에서 개인 데이터 탭을 선택한 다음, 삭제를 선택합니다.

Click here to confirm your account IEmailSender가 구현되지 않았고 종속성 주입 컨테이너에 등록되지 않았기 때문에 링크가 표시됩니다. 원본을 참조하세요.RegisterConfirmation

참고 항목

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

이메일 공급자 구성

이 자습서에서는 SendGrid를 사용하여 이메일을 보냅니다. 전자 메일을 보내려면 SendGrid 계정 및 키가 필요합니다. SMTP 대신 SendGrid 또는 다른 전자 메일 서비스를 사용하여 전자 메일을 보내는 것이 좋습니다. SMTP는 안전하게 보호하고 올바르게 설정하기가 어렵습니다.

SendGrid 계정에는 보낸 사람을 추가해야 할 수 있습니다.

보안 이메일 키를 페치하는 클래스를 만듭니다. 이 샘플의 경우 Services/AuthMessageSenderOptions.cs를 만듭니다.

namespace WebPWrecover.Services;

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

SendGrid 사용자 비밀 구성

비밀 관리자 도구를 사용하여 SendGridKey를 설정합니다. 예시:

dotnet user-secrets set SendGridKey <key>

Successfully saved SendGridKey to the secret store.

Windows에서 Secret Manager는 디렉터리의 파일에 키/값 쌍 secrets.json%APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId> 저장합니다.

파일의 secrets.json 내용은 암호화되지 않습니다. 다음 태그는 파일을 보여 줍니다 secrets.json . SendGridKey 값이 제거되었습니다.

{
  "SendGridKey": "<key removed>"
}

자세한 내용은 옵션 패턴구성을 참조하세요.

SendGrid 설치

이 자습서에서는 SendGrid를 통해 전자 메일 알림을 추가하는 방법을 보여 주지만 다른 전자 메일 공급업체를 사용할 수 있습니다.

SendGrid NuGet 패키지를 설치합니다.

패키지 관리자 콘솔에서 다음 명령을 입력합니다.

Install-Package SendGrid

무료 SendGrid 계정에 등록하려면 무료 SendGrid 시작을 참조하세요.

IEmailSender 구현

구현 IEmailSender하려면 다음과 유사한 코드를 사용하여 만듭니 Services/EmailSender.cs 다.

using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace WebPWrecover.Services;

public class EmailSender : IEmailSender
{
    private readonly ILogger _logger;

    public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
                       ILogger<EmailSender> logger)
    {
        Options = optionsAccessor.Value;
        _logger = logger;
    }

    public AuthMessageSenderOptions Options { get; } //Set with Secret Manager.

    public async Task SendEmailAsync(string toEmail, string subject, string message)
    {
        if (string.IsNullOrEmpty(Options.SendGridKey))
        {
            throw new Exception("Null SendGridKey");
        }
        await Execute(Options.SendGridKey, subject, message, toEmail);
    }

    public async Task Execute(string apiKey, string subject, string message, string toEmail)
    {
        var client = new SendGridClient(apiKey);
        var msg = new SendGridMessage()
        {
            From = new EmailAddress("Joe@contoso.com", "Password Recovery"),
            Subject = subject,
            PlainTextContent = message,
            HtmlContent = message
        };
        msg.AddTo(new EmailAddress(toEmail));

        // Disable click tracking.
        // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
        msg.SetClickTracking(false, false);
        var response = await client.SendEmailAsync(msg);
        _logger.LogInformation(response.IsSuccessStatusCode 
                               ? $"Email to {toEmail} queued successfully!"
                               : $"Failure Email to {toEmail}");
    }
}

전자 메일을 지원하도록 앱 구성

Program.cs 파일에 다음 코드를 추가합니다.

  • EmailSender를 임시 서비스로 추가합니다.
  • AuthMessageSenderOptions 구성 인스턴스를 등록합니다.
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

Account.RegisterConfirmation이 스캐폴드된 경우 기본 계정 확인을 사용하지 않도록 설정

이 섹션은 Account.RegisterConfirmation가 스캐폴드된 경우에만 적용됩니다. Account.RegisterConfirmation을 스캐폴드하지 않은 경우 이 섹션을 건너뜁니다.

사용자가 계정 확인을 위한 링크를 선택할 수 있는 Account.RegisterConfirmation으로 리디렉션됩니다. 기본 Account.RegisterConfirmation는 테스트용으로 사용되며 제작 앱에서 자동 계정 확인을 사용하지 않도록 설정해야 합니다.

확인된 계정을 요구하고 등록 시 즉시 로그인을 방지하려면 스캐폴드된 /Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs 파일에서 DisplayConfirmAccountLink = false를 설정합니다.

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;

namespace WebPWrecover.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class RegisterConfirmationModel : PageModel
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IEmailSender _sender;

        public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
        {
            _userManager = userManager;
            _sender = sender;
        }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string Email { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public bool DisplayConfirmAccountLink { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string EmailConfirmationUrl { get; set; }

        public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
        {
            if (email == null)
            {
                return RedirectToPage("/Index");
            }
            returnUrl = returnUrl ?? Url.Content("~/");

            var user = await _userManager.FindByEmailAsync(email);
            if (user == null)
            {
                return NotFound($"Unable to load user with email '{email}'.");
            }

            Email = email;
            // Once you add a real email sender, you should remove this code that lets you confirm the account
            DisplayConfirmAccountLink = false;
            if (DisplayConfirmAccountLink)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                EmailConfirmationUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                    protocol: Request.Scheme);
            }

            return Page();
        }
    }
}

이 단계는 Account.RegisterConfirmation가 스캐폴드된 경우에만 필요합니다. 스캐폴드되지 않은 RegisterConfirmation은 IEmailSender가 구현되고 종속성 주입 컨테이너등록된 시기를 자동으로 감지합니다.

등록, 이메일 확인 및 암호 재설정

웹앱을 실행하고 계정 확인 및 암호 복구 흐름을 테스트합니다.

  • 앱 실행 및 새 사용자 등록
  • 계정 확인 링크에 사용할 이메일을 확인합니다. 이메일을 받지 못하면 이메일 디버그를 참조하세요.
  • 링크를 클릭하여 이메일을 확인합니다.
  • 이메일 및 암호를 사용하여 로그인합니다.
  • 로그아웃.

암호 재설정 테스트

  • 로그인한 경우 로그아웃을 선택합니다.
  • 로그인 링크를 선택하고 암호를 잊으셨나요? 링크를 선택합니다.
  • 계정을 등록하는 데 사용한 이메일을 입력합니다.
  • 암호를 재설정하는 링크가 포함된 이메일이 전송됩니다. 이메일을 확인하고 링크를 클릭하여 암호를 다시 설정합니다. 암호를 성공적으로 재설정한 후에는 이메일 및 새 암호로 로그인할 수 있습니다.

이메일 확인 다시 보내기

전자 메일 확인 다시 보내기 링크를 로그인 페이지에서 선택합니다.

이메일 및 작업 시간 제한 변경

기본 비활성 시간 제한은 14일입니다. 다음 코드는 비활성 시간 제한을 5일로 설정합니다.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

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

var app = builder.Build();

// Code removed for brevity

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

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

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

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

var app = builder.Build();

// Code removed for brevity.

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

이메일 토큰 수명 변경

Identity 사용자 토큰의 기본 토큰 수명은 1일입니다. 이 섹션에서는 이메일 토큰 수명을 변경하는 방법을 보여 줍니다.

사용자 지정 DataProtectorTokenProvider<TUser>DataProtectionTokenProviderOptions를 추가합니다.

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

서비스 컨테이너에 사용자 지정 공급자를 추가합니다.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;
using WebPWrecover.TokenProviders;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(config =>
{
    config.SignIn.RequireConfirmedEmail = true;
    config.Tokens.ProviderMap.Add("CustomEmailConfirmation",
        new TokenProviderDescriptor(
            typeof(CustomEmailConfirmationTokenProvider<IdentityUser>)));
    config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
}).AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddTransient<CustomEmailConfirmationTokenProvider<IdentityUser>>();

builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

var app = builder.Build();

// Code removed for brevity.

이메일 디버그

이메일을 사용할 수 없는 경우:

  • EmailSender.Execute에서 중단점을 설정하여 SendGridClient.SendEmailAsync가 호출되는지 확인합니다.
  • EmailSender.Execute와 유사한 코드를 사용하여 이메일을 보내는 콘솔 앱을 만듭니다.
  • 이메일 활동 페이지를 검토합니다.
  • 스팸 폴더를 확인합니다.
  • 다른 이메일 공급자(Microsoft, Yahoo, Gmail 등)에서 다른 이메일 별칭을 사용해 보세요.
  • 다른 이메일 계정으로 전송해 보세요.

보안 모범 사례는 테스트 및 개발에 프로덕션 비밀을 사용하지 않는 것입니다. Azure에 앱을 게시하는 경우 Azure 웹앱 포털에서 SendGrid 비밀을 애플리케이션 설정으로 설정합니다. 구성 시스템은 환경 변수에서 키를 읽도록 설정됩니다.

소셜 및 로컬 로그인 계정 결합

이 섹션을 완료하려면 먼저 외부 인증 공급자를 사용하도록 설정해야 합니다. Facebook, Google 및 외부 공급자 인증을 참조하세요.

이메일 링크를 클릭하여 로컬 및 소셜 계정을 결합할 수 있습니다. 다음 시퀀스에서는 “RickAndMSFT@gmail.com”이 로컬 로그인으로 먼저 생성되지만 계정을 소셜 로그인으로 먼저 만든 다음, 로컬 로그인을 추가할 수 있습니다.

Web application: RickAndMSFT@gmail.com user authenticated

관리 링크를 클릭합니다. 이 계정과 연결된 외부(소셜 로그인)가 0개임을 확인합니다.

Manage view

다른 로그인 서비스에 대한 링크를 클릭하고 앱 요청을 수락합니다. 다음 이미지에서는 Facebook이 외부 인증 공급자입니다.

Manage your external logins view listing Facebook

두 계정이 결합되었습니다. 두 계정 중 하나를 사용하여 로그인할 수 있습니다. 소셜 로그인 인증 서비스가 다운되거나 소셜 계정에 대한 액세스 권한이 손실된 경우 사용자가 로컬 계정을 추가하도록 할 수 있습니다.

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

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

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

필수 조건

.NET Core 3.0 SDK 이상

인증을 사용하여 웹앱 만들기 및 테스트

다음 명령을 실행하여 인증을 사용하는 웹앱을 만듭니다.

dotnet new webapp -au Individual -uld -o WebPWrecover
cd WebPWrecover
dotnet run

앱을 실행하고 등록 링크를 선택한 후 사용자를 등록합니다. 등록되면 이메일 확인을 시뮬레이트하는 링크를 포함하는 /Identity/Account/RegisterConfirmation 페이지로 리디렉션됩니다.

  • Click here to confirm your account 링크를 선택합니다.
  • 로그인 링크를 선택하고 동일한 자격 증명을 사용하여 로그인합니다.
  • /Identity/Account/Manage/PersonalData 페이지로 리디렉션되는 Hello YourEmail@provider.com! 링크를 선택합니다.
  • 왼쪽에서 개인 데이터 탭을 선택한 다음, 삭제를 선택합니다.

이메일 공급자 구성

이 자습서에서는 SendGrid를 사용하여 이메일을 보냅니다. 다른 이메일 공급자를 사용할 수 있습니다. SendGrid 또는 다른 이메일 서비스를 사용하여 이메일을 보내는 것이 좋습니다. SMTP는 구성하기 어렵기 때문에 전자 메일이 스팸으로 표시되지 않습니다.

SendGrid 계정에는 보낸 사람을 추가해야 할 수 있습니다.

보안 이메일 키를 페치하는 클래스를 만듭니다. 이 샘플의 경우 Services/AuthMessageSenderOptions.cs를 만듭니다.

namespace WebPWrecover.Services;

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

SendGrid 사용자 비밀 구성

비밀 관리자 도구를 사용하여 SendGridKey를 설정합니다. 예시:

dotnet user-secrets set SendGridKey <SG.key>

Successfully saved SendGridKey = SG.keyVal to the secret store.

Windows에서 Secret Manager는 디렉터리의 파일에 키/값 쌍 secrets.json%APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId> 저장합니다.

파일의 secrets.json 내용은 암호화되지 않습니다. 다음 태그는 파일을 보여 줍니다 secrets.json . SendGridKey 값이 제거되었습니다.

{
  "SendGridKey": "<key removed>"
}

자세한 내용은 옵션 패턴구성을 참조하세요.

SendGrid 설치

이 자습서에서는 SendGrid를 통해 이메일 알림을 추가하는 방법을 보여 주지만 SMTP 및 기타 메커니즘을 사용하여 이메일을 보낼 수 있습니다.

SendGrid NuGet 패키지를 설치합니다.

패키지 관리자 콘솔에서 다음 명령을 입력합니다.

Install-Package SendGrid

무료 SendGrid 계정에 등록하려면 무료 SendGrid 시작을 참조하세요.

IEmailSender 구현

구현 IEmailSender하려면 다음과 유사한 코드를 사용하여 만듭니 Services/EmailSender.cs 다.

using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace WebPWrecover.Services;

public class EmailSender : IEmailSender
{
    private readonly ILogger _logger;

    public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
                       ILogger<EmailSender> logger)
    {
        Options = optionsAccessor.Value;
        _logger = logger;
    }

    public AuthMessageSenderOptions Options { get; } //Set with Secret Manager.

    public async Task SendEmailAsync(string toEmail, string subject, string message)
    {
        if (string.IsNullOrEmpty(Options.SendGridKey))
        {
            throw new Exception("Null SendGridKey");
        }
        await Execute(Options.SendGridKey, subject, message, toEmail);
    }

    public async Task Execute(string apiKey, string subject, string message, string toEmail)
    {
        var client = new SendGridClient(apiKey);
        var msg = new SendGridMessage()
        {
            From = new EmailAddress("Joe@contoso.com", "Password Recovery"),
            Subject = subject,
            PlainTextContent = message,
            HtmlContent = message
        };
        msg.AddTo(new EmailAddress(toEmail));

        // Disable click tracking.
        // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
        msg.SetClickTracking(false, false);
        var response = await client.SendEmailAsync(msg);
        _logger.LogInformation(response.IsSuccessStatusCode 
                               ? $"Email to {toEmail} queued successfully!"
                               : $"Failure Email to {toEmail}");
    }
}

이메일을 지원하도록 시작 구성

파일의 메서드 Startup.csConfigureServices 다음 코드를 추가합니다.

  • EmailSender를 임시 서비스로 추가합니다.
  • AuthMessageSenderOptions 구성 인스턴스를 등록합니다.
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

RegisterConfirmation 스캐폴드

스캐폴드IdentityAccount\RegisterConfirmation 스캐폴드에 대한 지침을 따릅니다.

Account.RegisterConfirmation이 스캐폴드된 경우 기본 계정 확인을 사용하지 않도록 설정

이 섹션은 Account.RegisterConfirmation가 스캐폴드된 경우에만 적용됩니다. Account.RegisterConfirmation을 스캐폴드하지 않은 경우 이 섹션을 건너뜁니다.

사용자가 계정 확인을 위한 링크를 선택할 수 있는 Account.RegisterConfirmation으로 리디렉션됩니다. 기본 Account.RegisterConfirmation는 테스트용으로 사용되며 제작 앱에서 자동 계정 확인을 사용하지 않도록 설정해야 합니다.

확인된 계정을 요구하고 등록 시 즉시 로그인을 방지하려면 스캐폴드된 /Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs 파일에서 DisplayConfirmAccountLink = false를 설정합니다.

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;

namespace WebPWrecover.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class RegisterConfirmationModel : PageModel
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IEmailSender _sender;

        public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
        {
            _userManager = userManager;
            _sender = sender;
        }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string Email { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public bool DisplayConfirmAccountLink { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string EmailConfirmationUrl { get; set; }

        public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
        {
            if (email == null)
            {
                return RedirectToPage("/Index");
            }
            returnUrl = returnUrl ?? Url.Content("~/");

            var user = await _userManager.FindByEmailAsync(email);
            if (user == null)
            {
                return NotFound($"Unable to load user with email '{email}'.");
            }

            Email = email;
            // Once you add a real email sender, you should remove this code that lets you confirm the account
            DisplayConfirmAccountLink = false;
            if (DisplayConfirmAccountLink)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                EmailConfirmationUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                    protocol: Request.Scheme);
            }

            return Page();
        }
    }
}

이 단계는 Account.RegisterConfirmation가 스캐폴드된 경우에만 필요합니다. 스캐폴드되지 않은 RegisterConfirmation은 IEmailSender가 구현되고 종속성 주입 컨테이너등록된 시기를 자동으로 감지합니다.

등록, 이메일 확인 및 암호 재설정

웹앱을 실행하고 계정 확인 및 암호 복구 흐름을 테스트합니다.

  • 앱 실행 및 새 사용자 등록
  • 계정 확인 링크에 사용할 이메일을 확인합니다. 이메일을 받지 못하면 이메일 디버그를 참조하세요.
  • 링크를 클릭하여 이메일을 확인합니다.
  • 이메일 및 암호를 사용하여 로그인합니다.
  • 로그아웃.

암호 재설정 테스트

  • 로그인한 경우 로그아웃을 선택합니다.
  • 로그인 링크를 선택하고 암호를 잊으셨나요? 링크를 선택합니다.
  • 계정을 등록하는 데 사용한 이메일을 입력합니다.
  • 암호를 재설정하는 링크가 포함된 이메일이 전송됩니다. 이메일을 확인하고 링크를 클릭하여 암호를 다시 설정합니다. 암호를 성공적으로 재설정한 후에는 이메일 및 새 암호로 로그인할 수 있습니다.

이메일 확인 다시 보내기

ASP.NET Core 5.0 이상에서는 로그인 페이지에서 이메일 확인 다시 보내기 링크를 선택합니다.

이메일 및 작업 시간 제한 변경

기본 비활성 시간 제한은 14일입니다. 다음 코드는 비활성 시간 제한을 5일로 설정합니다.

services.ConfigureApplicationCookie(o => {
    o.ExpireTimeSpan = TimeSpan.FromDays(5);
    o.SlidingExpiration = true;
});

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

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

public void ConfigureServices(IServiceCollection services)
{

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(
                  options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.Configure<DataProtectionTokenProviderOptions>(o =>
       o.TokenLifespan = TimeSpan.FromHours(3));

    services.AddTransient<IEmailSender, EmailSender>();
    services.Configure<AuthMessageSenderOptions>(Configuration);

    services.AddRazorPages();
}

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

이메일 토큰 수명 변경

Identity 사용자 토큰의 기본 토큰 수명은 1일입니다. 이 섹션에서는 이메일 토큰 수명을 변경하는 방법을 보여 줍니다.

사용자 지정 DataProtectorTokenProvider<TUser>DataProtectionTokenProviderOptions를 추가합니다.

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 void ConfigureServices(IServiceCollection services)
{

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(config =>
    {
        config.SignIn.RequireConfirmedEmail = true;
        config.Tokens.ProviderMap.Add("CustomEmailConfirmation",
            new TokenProviderDescriptor(
                typeof(CustomEmailConfirmationTokenProvider<IdentityUser>)));
        config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
      }).AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddTransient<CustomEmailConfirmationTokenProvider<IdentityUser>>();

    services.AddTransient<IEmailSender, EmailSender>();
    services.Configure<AuthMessageSenderOptions>(Configuration);

    services.AddRazorPages();
}

이메일 디버그

이메일을 사용할 수 없는 경우:

  • EmailSender.Execute에서 중단점을 설정하여 SendGridClient.SendEmailAsync가 호출되는지 확인합니다.
  • EmailSender.Execute와 유사한 코드를 사용하여 이메일을 보내는 콘솔 앱을 만듭니다.
  • 이메일 활동 페이지를 검토합니다.
  • 스팸 폴더를 확인합니다.
  • 다른 이메일 공급자(Microsoft, Yahoo, Gmail 등)에서 다른 이메일 별칭을 사용해 보세요.
  • 다른 이메일 계정으로 전송해 보세요.

보안 모범 사례는 테스트 및 개발에 프로덕션 비밀을 사용하지 않는 것입니다. Azure에 앱을 게시하는 경우 Azure 웹앱 포털에서 SendGrid 비밀을 애플리케이션 설정으로 설정합니다. 구성 시스템은 환경 변수에서 키를 읽도록 설정됩니다.

소셜 및 로컬 로그인 계정 결합

이 섹션을 완료하려면 먼저 외부 인증 공급자를 사용하도록 설정해야 합니다. Facebook, Google 및 외부 공급자 인증을 참조하세요.

이메일 링크를 클릭하여 로컬 및 소셜 계정을 결합할 수 있습니다. 다음 시퀀스에서는 “RickAndMSFT@gmail.com”이 로컬 로그인으로 먼저 생성되지만 계정을 소셜 로그인으로 먼저 만든 다음, 로컬 로그인을 추가할 수 있습니다.

Web application: RickAndMSFT@gmail.com user authenticated

관리 링크를 클릭합니다. 이 계정과 연결된 외부(소셜 로그인)가 0개임을 확인합니다.

Manage view

다른 로그인 서비스에 대한 링크를 클릭하고 앱 요청을 수락합니다. 다음 이미지에서는 Facebook이 외부 인증 공급자입니다.

Manage your external logins view listing Facebook

두 계정이 결합되었습니다. 두 계정 중 하나를 사용하여 로그인할 수 있습니다. 소셜 로그인 인증 서비스가 다운되거나 소셜 계정에 대한 액세스 권한이 손실된 경우 사용자가 로컬 계정을 추가하도록 할 수 있습니다.

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

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

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