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

작성자: Rick Anderson, PonantJoe Audette

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

사전 요구 사항

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

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

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

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

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

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

Click here to confirm your accountIEmailSender가 구현되지 않았고 디렉터리 삽입 컨테이너에 등록되지 않았기 때문에 링크가 표시됩니다. RegisterConfirmation 원본을 참조하세요.

이메일 공급자 구성

이 자습서에서는 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 . 스캐폴드되지 않은 RegisterConfirmationIEmailSender 가 구현되고 디렉터리 삽입 컨테이너 컨테이너 에 등록된 시기를 자동으로 검색합니다.

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

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

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

암호 재설정 테스트

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

이메일 확인 다시 보내기

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

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

기본 비활성 시간 제한은 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일입니다. 이 섹션에서는 이메일 토큰 수명을 변경하는 방법을 보여 줍니다.

사용자 지정을 DataProtectionTokenProviderOptions추가하고 다음을 수행합니다DataProtectorTokenProvider<TUser>.

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”이 로컬 로그인으로 먼저 생성되지만 계정을 소셜 로그인으로 먼저 만든 다음, 로컬 로그인을 추가할 수 있습니다.

웹 애플리케이션: RickAndMSFT@gmail.com 사용자 인증

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

보기 관리

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

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 . 스캐폴드되지 않은 RegisterConfirmationIEmailSender 가 구현되고 디렉터리 삽입 컨테이너 컨테이너 에 등록된 시기를 자동으로 검색합니다.

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

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

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

암호 재설정 테스트

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

이메일 확인 다시 보내기

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일입니다. 이 섹션에서는 이메일 토큰 수명을 변경하는 방법을 보여 줍니다.

사용자 지정을 DataProtectionTokenProviderOptions추가하고 다음을 수행합니다DataProtectorTokenProvider<TUser>.

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”이 로컬 로그인으로 먼저 생성되지만 계정을 소셜 로그인으로 먼저 만든 다음, 로컬 로그인을 추가할 수 있습니다.

웹 애플리케이션: RickAndMSFT@gmail.com 사용자 인증

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

보기 관리

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

Facebook이 나열된 외부 로그인 보기 관리

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

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

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

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