ASP.NET Core에서 Identity 소개

작성자: Rick Anderson

ASP.NET Core Identity:

  • UI(사용자 인터페이스) 로그인 기능을 지원하는 API입니다.
  • 사용자, 암호, 프로필 데이터, 역할, 클레임, 토큰, 이메일 확인 등을 관리합니다.

사용자는 Identity에 저장된 로그인 정보를 사용하여 계정을 만들거나 외부 로그인 공급자를 사용할 수 있습니다. 지원되는 외부 로그인 공급자에는 Facebook, Google, Microsoft 계정 및 Twitter가 포함됩니다.

모든 사용자의 인증을 전역으로 요구하는 방법에 대한 자세한 내용은 인증된 사용자 요구를 참조하세요.

Identity 소스 코드는 GitHub에서 사용할 수 있습니다. Identity를 스캐폴드하고 생성된 파일을 확인하여 Identity와의 템플릿 상호 작용을 검토합니다.

Identity는 일반적으로 SQL Server 데이터베이스를 사용하여 사용자 이름, 암호 및 프로필 데이터를 저장하도록 구성됩니다. 또는 다른 영구 저장소(예: Azure Table Storage)를 사용할 수 있습니다.

이 항목에서는 Identity를 사용하여 사용자를 등록, 로그인 및 로그아웃하는 방법을 알아봅니다. 참고: 템플릿은 사용자 이름과 이메일을 사용자에 대해 동일하게 처리합니다. Identity를 사용하는 앱을 만드는 방법에 대한 자세한 지침은 다음 단계를 참조하세요.

ASP.NET Core Identity는 Microsoft identity 플랫폼과는 관련이 없습니다. Microsoft ID 플랫폼은 다음과 같습니다.

  • Azure AD(Azure Active Directory) 개발자 플랫폼의 진화
  • ASP.NET Core 앱에서 인증 및 권한 부여를 위한 대체 ID 솔루션입니다.

ASP.NET Core Identity는 ASP.NET Core 웹앱에 UI(사용자 인터페이스) 로그인 기능을 추가합니다. 웹 API 및 SPA를 보호하려면 다음 중 하나를 사용합니다.

Duende Identity Server는 ASP.NET Core용 OpenID Connect 및 OAuth 2.0 프레임워크입니다. Duende Identity Server에서는 다음과 같은 보안 기능을 사용할 수 있습니다.

  • AaaS(Authentication as a Service)
  • 여러 응용 프로그램 유형에 대한 SSO(Single Sign-On/Off)
  • API에 대한 액세스 제어
  • 페더레이션 게이트웨이

Important

Duende Software에서 Duende Identity 서버의 프로덕션 사용에 대한 라이선스 요금 지불을 요구할 수 있습니다. 자세한 내용은 ASP.NET Core 5.0에서 6.0으로 마이그레이션을 참조하세요.

자세한 내용은 Duende Identity Server 설명서(Duende Software 웹 사이트)를 참조하세요.

샘플 코드 보기 및 다운로드(다운로드 방법)

인증을 사용하여 웹앱 만들기

개별 사용자 계정을 사용하여 ASP.NET Core 웹 애플리케이션 프로젝트를 만듭니다.

  • ASP.NET Core 웹앱 템플릿을 선택합니다. 프로젝트 이름을 WebApp1로 지정하여 프로젝트 다운로드와 동일한 네임스페이스를 갖습니다. 확인을 클릭합니다.
  • 인증 유형 입력에서 개별 사용자 계정을 선택합니다.

생성된 프로젝트는 ASP.NET CoreIdentityRazor 클래스 라이브러리로서 제공합니다. IdentityRazor 클래스 라이브러리는 Identity 영역이 있는 엔드포인트를 노출합니다. 예시:

  • /Identity/Account/Login
  • /Identity/Account/Logout
  • /Identity/Account/Manage

마이그레이션 적용

마이그레이션을 적용하여 데이터베이스를 초기화합니다.

PMC(패키지 관리자 콘솔)에서 다음 명령을 실행합니다.

Update-Database

테스트 등록 및 로그인

앱을 실행하고 사용자를 등록합니다. 화면 크기에 따라 탐색 토글 단추를 선택하여 등록로그인 링크를 확인해야 할 수 있습니다.

Identity 데이터베이스 보기

  • 보기 메뉴에서 SQL Server 개체 탐색기(SSOX)를 선택합니다.
  • (localdb)MSSQLLocalDB(SQL Server 13)로 이동합니다. 마우스 오른쪽 단추로 dbo.AspNetUsers>데이터 보기를 클릭합니다.

Contextual menu on AspNetUsers table in SQL Server Object Explorer

Identity 서비스 구성

서비스는 Program.cs에 추가됩니다. 일반적인 패턴은 다음 순서로 메서드를 호출하는 것입니다.

  1. Add{Service}
  2. builder.Services.Configure{Service}
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;

var builder = WebApplication.CreateBuilder(args);

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

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

builder.Services.Configure<IdentityOptions>(options =>
{
    // Password settings.
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequiredLength = 6;
    options.Password.RequiredUniqueChars = 1;

    // Lockout settings.
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    // User settings.
    options.User.AllowedUserNameCharacters =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
    options.User.RequireUniqueEmail = false;
});

builder.Services.ConfigureApplicationCookie(options =>
{
    // Cookie settings
    options.Cookie.HttpOnly = true;
    options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

    options.LoginPath = "/Identity/Account/Login";
    options.AccessDeniedPath = "/Identity/Account/AccessDenied";
    options.SlidingExpiration = true;
});

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

위의 코드는 Identity를 기본 옵션 값으로 구성합니다. 서비스는 종속성 주입을 통해 앱에서 사용할 수 있습니다.

UseAuthentication을 호출하여 Identity를 사용할 수 있습니다. UseAuthentication은 인증 미들웨어를 요청 파이프라인에 추가합니다.

템플릿에서 생성된 앱은 권한 부여를 사용하지 않습니다. app.UseAuthorization은 앱이 권한 부여를 추가하는 경우 올바른 순서로 추가되도록 하기 위해 포함됩니다. UseRouting, UseAuthentication, UseAuthorization는 이전 코드에 표시된 순서대로 호출되어야 합니다.

IdentityOptions에 대한 자세한 내용은 IdentityOptions애플리케이션 시작을 참조하세요.

Register, Login, LogOut 및 RegisterConfirmation 스캐폴드

Register, Login, LogOutRegisterConfirmation 파일을 추가합니다. 권한 부여를 사용하여 Razor 프로젝트에 ID 스캐폴드 지침에 따라 이 섹션에 표시된 코드를 생성합니다.

레지스터 검사

사용자가 Register 페이지에서 등록 단추를 클릭하면 RegisterModel.OnPostAsync 작업이 호출됩니다. 사용자는 개체에 의해 CreateAsync(TUser) 생성됩니다._userManager

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
                                          .ToList();
    if (ModelState.IsValid)
    {
        var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
        var result = await _userManager.CreateAsync(user, Input.Password);
        if (result.Succeeded)
        {
            _logger.LogInformation("User created a new account with password.");

            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            var callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = user.Id, code = code },
                protocol: Request.Scheme);

            await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

            if (_userManager.Options.SignIn.RequireConfirmedAccount)
            {
                return RedirectToPage("RegisterConfirmation", 
                                      new { email = Input.Email });
            }
            else
            {
                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

기본 계정 확인 사용 안 함

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

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

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

    public string Email { get; set; }

    public bool DisplayConfirmAccountLink { get; set; }

    public string EmailConfirmationUrl { get; set; }

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

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

로그인

로그인 양식은 다음과 같은 경우에 표시됩니다.

  • 로그인 링크가 선택되어 있습니다.
  • 사용자가 액세스 권한이 없거나 또는 시스템에서 인증되지 않은 제한된 페이지에 액세스하려고 합니다.

로그인 페이지에 양식이 제출되면 OnPostAsync 작업이 호출됩니다. PasswordSignInAsync_signInManager 개체에서 호출됩니다.

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, 
        // set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email,
                           Input.Password, Input.RememberMe, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new
            {
                ReturnUrl = returnUrl,
                RememberMe = Input.RememberMe
            });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

권한 부여 결정을 내리는 방법에 대한 자세한 내용은 ASP.NET Core 권한 부여 소개를 참조하세요.

로그아웃

로그아웃 링크는 LogoutModel.OnPost 작업을 호출합니다.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace WebApp1.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LogoutModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;

        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
        {
            _signInManager = signInManager;
            _logger = logger;
        }

        public void OnGet()
        {
        }

        public async Task<IActionResult> OnPost(string returnUrl = null)
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation("User logged out.");
            if (returnUrl != null)
            {
                return LocalRedirect(returnUrl);
            }
            else
            {
                return RedirectToPage();
            }
        }
    }
}

앞의 코드에서 return RedirectToPage(); 코드는 브라우저가 새 요청을 수행하고 사용자의 ID가 업데이트되도록 리디렉션이어야 합니다.

SignOutAsync 에 저장된 사용자의 클레임을 cookie지웁니다.

게시물은 Pages/Shared/_LoginPartial.cshtml에서 자세하게 다루고 있습니다.

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" 
                                              title="Manage">Hello @User.Identity.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" 
                                  asp-route-returnUrl="@Url.Page("/", new { area = "" })" 
                                  method="post" >
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

Identity 테스트

기본 웹 프로젝트 템플릿은 홈 페이지에 대한 익명 액세스를 허용합니다. Identity를 테스트하려면 [Authorize]를 추가합니다.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace WebApp1.Pages
{
    [Authorize]
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

로그인한 경우 로그아웃합니다. 앱을 실행하고 Privacy 링크를 선택합니다. 로그인 페이지로 리디렉션됩니다.

Identity 탐색

Identity를 자세히 살펴보려면 다음을 수행합니다.

Identity 구성 요소

모든 Identity 종속 NuGet 패키지는 ASP.NET Core 공유 프레임워크에 포함됩니다.

Identity의 기본 패키지는 Microsoft.AspNetCore.Identity입니다. 이 패키지는 Microsoft.AspNetCore.Identity.EntityFrameworkCore로서 포함된, ASP.NET Core Identity에 대한 핵심 인터페이스 집합을 포함합니다.

ASP.NET Core Identity 마이그레이션

기존 Identity 저장소 마이그레이션에 대한 자세한 내용 및 지침은 인증 및 Identity 마이그레이션을 참조하세요.

암호 강도 설정

최소 암호 요구 사항을 설정하는 샘플은 구성을 참조하세요.

AddDefaultIdentity 및 AddIdentity

AddDefaultIdentity는 ASP.NET Core 2.1에서 도입되었습니다. AddDefaultIdentity를 호출하는 것은 다음을 호출하는 것과 비슷합니다.

자세한 내용은 AddDefault Identity 원본을 참조하세요.

정적 Identity 자산 게시 방지

정적 Identity 자산(Identity UI용 스타일시트 및 JavaScript 파일)을 웹 루트에 게시하지 않도록 하려면 다음 ResolveStaticWebAssetsInputsDependsOn 속성 및 RemoveIdentityAssets 대상을 앱의 프로젝트 파일에 추가합니다.

<PropertyGroup>
  <ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>

<Target Name="RemoveIdentityAssets">
  <ItemGroup>
    <StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
  </ItemGroup>
</Target>

다음 단계

작성자: Rick Anderson

ASP.NET Core Identity:

  • UI(사용자 인터페이스) 로그인 기능을 지원하는 API입니다.
  • 사용자, 암호, 프로필 데이터, 역할, 클레임, 토큰, 이메일 확인 등을 관리합니다.

사용자는 Identity에 저장된 로그인 정보를 사용하여 계정을 만들거나 외부 로그인 공급자를 사용할 수 있습니다. 지원되는 외부 로그인 공급자에는 Facebook, Google, Microsoft 계정 및 Twitter가 포함됩니다.

모든 사용자의 인증을 전역으로 요구하는 방법에 대한 자세한 내용은 인증된 사용자 요구를 참조하세요.

Identity 소스 코드는 GitHub에서 사용할 수 있습니다. Identity를 스캐폴드하고 생성된 파일을 확인하여 Identity와의 템플릿 상호 작용을 검토합니다.

Identity는 일반적으로 SQL Server 데이터베이스를 사용하여 사용자 이름, 암호 및 프로필 데이터를 저장하도록 구성됩니다. 또는 다른 영구 저장소(예: Azure Table Storage)를 사용할 수 있습니다.

이 항목에서는 Identity를 사용하여 사용자를 등록, 로그인 및 로그아웃하는 방법을 알아봅니다. 참고: 템플릿은 사용자 이름과 이메일을 사용자에 대해 동일하게 처리합니다. Identity를 사용하는 앱을 만드는 방법에 대한 자세한 지침은 다음 단계를 참조하세요.

Microsoft ID 플랫폼은 다음과 같습니다.

  • Azure AD(Azure Active Directory) 개발자 플랫폼의 진화
  • ASP.NET Core 앱에서 인증 및 권한 부여를 위한 대체 ID 솔루션입니다.
  • ASP.NET Core Identity관련이 없습니다.

ASP.NET Core Identity는 ASP.NET Core 웹앱에 UI(사용자 인터페이스) 로그인 기능을 추가합니다. 웹 API 및 SPA를 보호하려면 다음 중 하나를 사용합니다.

Duende IdentityServer는 ASP.NET Core용 OpenID Connect 및 OAuth 2.0 프레임워크입니다. Duende IdentityServer에서는 다음과 같은 보안 기능을 사용할 수 있습니다.

  • AaaS(Authentication as a Service)
  • 여러 응용 프로그램 유형에 대한 SSO(Single Sign-On/Off)
  • API에 대한 액세스 제어
  • 페더레이션 게이트웨이

자세한 내용은 Duende IdentityServer 개요를 참조하세요.

다른 인증 공급자에 대한 자세한 내용은 ASP.NET Core 대한 커뮤니티 OSS 인증 옵션을 참조하세요.

샘플 코드 보기 및 다운로드(다운로드 방법)

인증을 사용하여 웹앱 만들기

개별 사용자 계정을 사용하여 ASP.NET Core 웹 애플리케이션 프로젝트를 만듭니다.

  • 파일>>프로젝트를 선택합니다.
  • 새 ASP.NET Core 웹 애플리케이션을 선택합니다. 프로젝트 이름을 WebApp1로 지정하여 프로젝트 다운로드와 동일한 네임스페이스를 갖습니다. 확인을 클릭합니다.
  • ASP.NET Core 웹 애플리케이션을 선택한 다음, 인증 변경을 선택합니다.
  • 개별 사용자 계정을 선택하고 확인을 클릭합니다.

생성된 프로젝트는 ASP.NET CoreIdentityRazor 클래스 라이브러리로서 제공합니다. IdentityRazor 클래스 라이브러리는 Identity 영역이 있는 엔드포인트를 노출합니다. 예시:

  • /Identity/Account/Login
  • /Identity/Account/Logout
  • /Identity/Account/Manage

마이그레이션 적용

마이그레이션을 적용하여 데이터베이스를 초기화합니다.

PMC(패키지 관리자 콘솔)에서 다음 명령을 실행합니다.

PM> Update-Database

테스트 등록 및 로그인

앱을 실행하고 사용자를 등록합니다. 화면 크기에 따라 탐색 토글 단추를 선택하여 등록로그인 링크를 확인해야 할 수 있습니다.

Identity 데이터베이스 보기

  • 보기 메뉴에서 SQL Server 개체 탐색기(SSOX)를 선택합니다.
  • (localdb)MSSQLLocalDB(SQL Server 13)로 이동합니다. 마우스 오른쪽 단추로 dbo.AspNetUsers>데이터 보기를 클릭합니다.

Contextual menu on AspNetUsers table in SQL Server Object Explorer

Identity 서비스 구성

서비스는 ConfigureServices에 추가됩니다. 일반적인 패턴은 모든 Add{Service} 메서드를 호출한 후 모든 services.Configure{Service} 메서드를 호출하는 것입니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
     // options.UseSqlite(
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();

    services.Configure<IdentityOptions>(options =>
    {
        // Password settings.
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = false;
    });

    services.ConfigureApplicationCookie(options =>
    {
        // Cookie settings
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

        options.LoginPath = "/Identity/Account/Login";
        options.AccessDeniedPath = "/Identity/Account/AccessDenied";
        options.SlidingExpiration = true;
    });
}

위의 강조 표시된 코드는 Identity를 기본 옵션 값으로 구성합니다. 서비스는 종속성 주입을 통해 앱에서 사용할 수 있습니다.

UseAuthentication을 호출하여 Identity를 사용할 수 있습니다. UseAuthentication은 인증 미들웨어를 요청 파이프라인에 추가합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

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

    app.UseRouting();

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

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        // options.UseSqlite(
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();

    services.Configure<IdentityOptions>(options =>
    {
        // Password settings.
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = false;
    });

    services.ConfigureApplicationCookie(options =>
    {
        // Cookie settings
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

        options.LoginPath = "/Identity/Account/Login";
        options.AccessDeniedPath = "/Identity/Account/AccessDenied";
        options.SlidingExpiration = true;
    });
}

위의 코드는 Identity를 기본 옵션 값으로 구성합니다. 서비스는 종속성 주입을 통해 앱에서 사용할 수 있습니다.

UseAuthentication을 호출하여 Identity를 사용할 수 있습니다. UseAuthentication은 인증 미들웨어를 요청 파이프라인에 추가합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

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

    app.UseRouting();

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

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

템플릿에서 생성된 앱은 권한 부여를 사용하지 않습니다. app.UseAuthorization은 앱이 권한 부여를 추가하는 경우 올바른 순서로 추가되도록 하기 위해 포함됩니다. UseRouting, UseAuthentication, UseAuthorizationUseEndpoints는 이전 코드에 표시된 순서대로 호출되어야 합니다.

IdentityOptionsStartup에 대한 자세한 내용은 IdentityOptions애플리케이션 시작을 참조하세요.

Register, Login, LogOut 및 RegisterConfirmation 스캐폴드

Register, Login, LogOutRegisterConfirmation 파일을 추가합니다. 권한 부여를 사용하여 Razor 프로젝트에 ID 스캐폴드 지침에 따라 이 섹션에 표시된 코드를 생성합니다.

레지스터 검사

사용자가 Register 페이지에서 등록 단추를 클릭하면 RegisterModel.OnPostAsync 작업이 호출됩니다. 사용자는 개체에 의해 CreateAsync(TUser) 생성됩니다._userManager

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
                                          .ToList();
    if (ModelState.IsValid)
    {
        var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
        var result = await _userManager.CreateAsync(user, Input.Password);
        if (result.Succeeded)
        {
            _logger.LogInformation("User created a new account with password.");

            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            var callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = user.Id, code = code },
                protocol: Request.Scheme);

            await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

            if (_userManager.Options.SignIn.RequireConfirmedAccount)
            {
                return RedirectToPage("RegisterConfirmation", 
                                      new { email = Input.Email });
            }
            else
            {
                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

기본 계정 확인 사용 안 함

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

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

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

    public string Email { get; set; }

    public bool DisplayConfirmAccountLink { get; set; }

    public string EmailConfirmationUrl { get; set; }

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

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

로그인

로그인 양식은 다음과 같은 경우에 표시됩니다.

  • 로그인 링크가 선택되어 있습니다.
  • 사용자가 액세스 권한이 없거나 또는 시스템에서 인증되지 않은 제한된 페이지에 액세스하려고 합니다.

로그인 페이지에 양식이 제출되면 OnPostAsync 작업이 호출됩니다. PasswordSignInAsync_signInManager 개체에서 호출됩니다.

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, 
        // set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email,
                           Input.Password, Input.RememberMe, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new
            {
                ReturnUrl = returnUrl,
                RememberMe = Input.RememberMe
            });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

권한 부여 결정을 내리는 방법에 대한 자세한 내용은 ASP.NET Core 권한 부여 소개를 참조하세요.

로그아웃

로그아웃 링크는 LogoutModel.OnPost 작업을 호출합니다.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace WebApp1.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LogoutModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;

        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
        {
            _signInManager = signInManager;
            _logger = logger;
        }

        public void OnGet()
        {
        }

        public async Task<IActionResult> OnPost(string returnUrl = null)
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation("User logged out.");
            if (returnUrl != null)
            {
                return LocalRedirect(returnUrl);
            }
            else
            {
                return RedirectToPage();
            }
        }
    }
}

앞의 코드에서 return RedirectToPage(); 코드는 브라우저가 새 요청을 수행하고 사용자의 ID가 업데이트되도록 리디렉션이어야 합니다.

SignOutAsync 에 저장된 사용자의 클레임을 cookie지웁니다.

게시물은 Pages/Shared/_LoginPartial.cshtml에서 자세하게 다루고 있습니다.

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" 
                                              title="Manage">Hello @User.Identity.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" 
                                  asp-route-returnUrl="@Url.Page("/", new { area = "" })" 
                                  method="post" >
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

Identity 테스트

기본 웹 프로젝트 템플릿은 홈 페이지에 대한 익명 액세스를 허용합니다. Identity를 테스트하려면 [Authorize]를 추가합니다.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace WebApp1.Pages
{
    [Authorize]
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

로그인한 경우 로그아웃합니다. 앱을 실행하고 Privacy 링크를 선택합니다. 로그인 페이지로 리디렉션됩니다.

Identity 탐색

Identity를 자세히 살펴보려면 다음을 수행합니다.

Identity 구성 요소

모든 Identity 종속 NuGet 패키지는 ASP.NET Core 공유 프레임워크에 포함됩니다.

Identity의 기본 패키지는 Microsoft.AspNetCore.Identity입니다. 이 패키지는 Microsoft.AspNetCore.Identity.EntityFrameworkCore로서 포함된, ASP.NET Core Identity에 대한 핵심 인터페이스 집합을 포함합니다.

ASP.NET Core Identity 마이그레이션

기존 Identity 저장소 마이그레이션에 대한 자세한 내용 및 지침은 인증 및 Identity 마이그레이션을 참조하세요.

암호 강도 설정

최소 암호 요구 사항을 설정하는 샘플은 구성을 참조하세요.

정적 Identity 자산 게시 방지

정적 Identity 자산(Identity UI용 스타일시트 및 JavaScript 파일)을 웹 루트에 게시하지 않도록 하려면 다음 ResolveStaticWebAssetsInputsDependsOn 속성 및 RemoveIdentityAssets 대상을 앱의 프로젝트 파일에 추가합니다.

<PropertyGroup>
  <ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>

<Target Name="RemoveIdentityAssets">
  <ItemGroup>
    <StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
  </ItemGroup>
</Target>

다음 단계