ASP.NET Core 2.0으로 인증 및 Identity 마이그레이션

작성자: Scott AddieHao Kung

ASP.NET Core 2.0에는 인증 및 Identity를 위한 새 모델이 있으며 서비스를 사용하여 구성을 간소화합니다. 인증 또는 Identity를 사용하는 ASP.NET Core 1.x 애플리케이션은 다음에 설명된 대로 새 모델을 사용하도록 업데이트될 수 있습니다.

네임스페이스 업데이트

1.x에서 IdentityRoleIdentityUser와 같은 클래스는 Microsoft.AspNetCore.Identity.EntityFrameworkCore 네임스페이스에 있습니다.

2.0에서 Microsoft.AspNetCore.Identity 네임스페이스는 이러한 클래스 중 일부에 대한 새 홈이 됩니다. 기본 Identity 코드를 사용하는 경우 영향을 받는 클래스에는 ApplicationUserStartup이 포함됩니다. 영향을 받는 참조를 확인하도록 using 문을 조정합니다.

인증 미들웨어 및 서비스

1.x 프로젝트에서 인증은 미들웨어를 통해 구성됩니다. 지원하려는 각 인증 체계에 대해 미들웨어 메서드가 호출됩니다.

다음 1.x 예제에서는 다음에서 Facebook 인증을 Identity 구성합니다.Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
    app.UseIdentity();
    app.UseFacebookAuthentication(new FacebookOptions {
        AppId = Configuration["auth:facebook:appid"],
        AppSecret = Configuration["auth:facebook:appsecret"]
    });
}

2.0 프로젝트에서 인증은 서비스를 통해 구성됩니다. 각 인증 체계는 .의 Startup.cs메서드에 ConfigureServices 등록됩니다. UseIdentity 메서드는 UseAuthentication으로 대체됩니다.

다음 2.0 예제에서는 다음에서 Facebook 인증을 Identity 구성합니다.Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

    // If you want to tweak Identity cookies, they're no longer part of IdentityOptions.
    services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
    services.AddAuthentication()
            .AddFacebook(options =>
            {
                options.AppId = Configuration["auth:facebook:appid"];
                options.AppSecret = Configuration["auth:facebook:appsecret"];
            });
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) {
    app.UseAuthentication();
}

UseAuthentication 메서드는 자동 인증 및 원격 인증 요청 처리를 담당하는 단일 인증 미들웨어 구성 요소를 추가합니다. 모든 개별 미들웨어 구성 요소를 하나의 공통 미들웨어 구성 요소로 바꿉니다.

각 주요 인증 체계에 대한 2.0 마이그레이션 지침은 다음과 같습니다.

아래의 두 옵션 중 하나를 선택하고 다음에서 필요한 내용을 변경합니다.Startup.cs

  1. Identity에서 cookie 사용

    • UseIdentityConfigure 메서드의 UseAuthentication으로 대체합니다.

      app.UseAuthentication();
      
    • ConfigureServices 메서드에서 AddIdentity 메서드를 호출하여 cookie 인증 서비스를 추가합니다.

    • 필요에 따라 ConfigureServices 메서드에서 ConfigureApplicationCookie 또는 ConfigureExternalCookie 메서드를 호출하여 Identitycookie 설정을 조정합니다.

      services.AddIdentity<ApplicationUser, IdentityRole>()
              .AddEntityFrameworkStores<ApplicationDbContext>()
              .AddDefaultTokenProviders();
      
      services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
      
  2. Identity 없이 cookie 사용

    • Configure 메서드의 UseCookieAuthentication 메서드 호출을 UseAuthentication으로 바꿉니다.

      app.UseAuthentication();
      
    • ConfigureServices 메서드에서 AddAuthenticationAddCookie 메서드를 호출합니다.

      // If you don't want the cookie to be automatically authenticated and assigned to HttpContext.User,
      // remove the CookieAuthenticationDefaults.AuthenticationScheme parameter passed to AddAuthentication.
      services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
              .AddCookie(options =>
              {
                  options.LoginPath = "/Account/LogIn";
                  options.LogoutPath = "/Account/LogOff";
              });
      

JWT 전달자 인증

Startup.cs에서 다음과 같이 변경합니다.

  • Configure 메서드의 UseJwtBearerAuthentication 메서드 호출을 UseAuthentication으로 바꿉니다.

    app.UseAuthentication();
    
  • ConfigureServices 메서드에서 AddJwtBearer 메서드를 호출합니다.

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.Audience = "http://localhost:5001/";
                options.Authority = "http://localhost:5000/";
            });
    

    이 코드 조각에서는 Identity를 사용하지 않으므로 JwtBearerDefaults.AuthenticationSchemeAddAuthentication메서드에 전달하여 기본 체계를 설정해야 합니다.

OIDC(OpenID Connect) 인증

Startup.cs에서 다음과 같이 변경합니다.

  • Configure 메서드의 UseOpenIdConnectAuthentication 메서드 호출을 UseAuthentication으로 바꿉니다.

    app.UseAuthentication();
    
  • ConfigureServices 메서드에서 AddOpenIdConnect 메서드를 호출합니다.

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.Authority = Configuration["auth:oidc:authority"];
        options.ClientId = Configuration["auth:oidc:clientid"];
    });
    
  • OpenIdConnectOptions 작업의 PostLogoutRedirectUri 속성을 SignedOutRedirectUri로 바꿉니다.

    .AddOpenIdConnect(options =>
    {
        options.SignedOutRedirectUri = "https://contoso.com";
    });
    

Facebook 인증

Startup.cs에서 다음과 같이 변경합니다.

  • Configure 메서드의 UseFacebookAuthentication 메서드 호출을 UseAuthentication으로 바꿉니다.

    app.UseAuthentication();
    
  • ConfigureServices 메서드에서 AddFacebook 메서드를 호출합니다.

    services.AddAuthentication()
            .AddFacebook(options =>
            {
                options.AppId = Configuration["auth:facebook:appid"];
                options.AppSecret = Configuration["auth:facebook:appsecret"];
            });
    

Google 인증

Startup.cs에서 다음과 같이 변경합니다.

  • Configure 메서드의 UseGoogleAuthentication 메서드 호출을 UseAuthentication으로 바꿉니다.

    app.UseAuthentication();
    
  • ConfigureServices 메서드에서 AddGoogle 메서드를 호출합니다.

    services.AddAuthentication()
            .AddGoogle(options =>
            {
                options.ClientId = Configuration["auth:google:clientid"];
                options.ClientSecret = Configuration["auth:google:clientsecret"];
            });
    

Microsoft 계정 인증

Microsoft 계정 인증에 대한 자세한 내용은 이 GitHub 문제를 참조하세요.

Startup.cs에서 다음과 같이 변경합니다.

  • Configure 메서드의 UseMicrosoftAccountAuthentication 메서드 호출을 UseAuthentication으로 바꿉니다.

    app.UseAuthentication();
    
  • ConfigureServices 메서드에서 AddMicrosoftAccount 메서드를 호출합니다.

    services.AddAuthentication()
            .AddMicrosoftAccount(options =>
            {
                options.ClientId = Configuration["auth:microsoft:clientid"];
                options.ClientSecret = Configuration["auth:microsoft:clientsecret"];
            });
    

Twitter 인증

Startup.cs에서 다음과 같이 변경합니다.

  • Configure 메서드의 UseTwitterAuthentication 메서드 호출을 UseAuthentication으로 바꿉니다.

    app.UseAuthentication();
    
  • ConfigureServices 메서드에서 AddTwitter 메서드를 호출합니다.

    services.AddAuthentication()
            .AddTwitter(options =>
            {
                options.ConsumerKey = Configuration["auth:twitter:consumerkey"];
                options.ConsumerSecret = Configuration["auth:twitter:consumersecret"];
            });
    

기본 인증 체계 설정

1.x AutomaticAuthenticate 에서는 기본 클래스의 AuthenticationOptions 속성과 AutomaticChallenge 속성을 단일 인증 체계로 설정해야 했습니다. 이를 적용하는 좋은 방법은 없습니다.

2.0에서는 이러한 두 속성이 개별 AuthenticationOptions 인스턴스에서 속성으로 제거되었습니다. 다음 메서드 Startup.cs내의 AddAuthentication 메서드 호출 ConfigureServices 에서 구성할 수 있습니다.

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

위의 코드 조각에서 기본 체계는 CookieAuthenticationDefaults.AuthenticationScheme("Cookie")으로 설정됩니다.

또는 오버로드된 버전의 AddAuthentication 메서드를 사용하여 둘 이상의 속성을 설정합니다. 다음 오버로드된 메서드 예제에서 기본 체계는 CookieAuthenticationDefaults.AuthenticationScheme으로 설정됩니다. 개별 [Authorize] 특성 또는 권한 부여 정책 내에서 인증 체계를 지정할 수 있습니다.

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});

다음 조건 중 하나에 해당하는 경우 2.0에서 기본 스키마를 정의합니다.

  • 사용자에게 자동으로 로그인하려고 합니다.
  • 스키마를 지정하지 않고 [Authorize] 특성 또는 권한 부여 정책을 사용합니다.

이 규칙의 예외는 AddIdentity 메서드입니다. 이 메서드는 cookie를 추가하고 애플리케이션 cookieIdentityConstants.ApplicationScheme에 기본 인증 및 챌린지 체계를 설정합니다. 또한 기본 로그인 체계를 외부 cookieIdentityConstants.ExternalScheme으로 설정합니다.

HttpContext 인증 확장 프로그램 사용

IAuthenticationManager 인터페이스는 1.x 인증 시스템의 기본 진입점입니다. Microsoft.AspNetCore.Authentication 네임스페이스의 새로운 HttpContext 확장 메서드 집합으로 대체되었습니다.

예를 들어 1.x 프로젝트는 Authentication 속성을 참조합니다.

// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);

2.0 프로젝트에서 Microsoft.AspNetCore.Authentication 네임스페이스를 가져오고 Authentication 속성 참조를 삭제합니다.

// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Windows 인증(HTTP.sys/IISIntegration)

Windows 인증에는 두 가지 변형이 있습니다.

  • 호스트는 인증된 사용자만 허용합니다. 이러한 변형은 2.0 변경의 영향을 받지 않습니다.

  • 호스트는 익명 및 인증된 사용자를 모두 허용합니다. 이러한 변형은 2.0 변경의 영향을 받습니다. 예를 들어 앱은 IIS 또는 HTTP.sys 계층에서 익명 사용자를 허용하지만 컨트롤러 수준에서 사용자에게 권한을 부여해야 합니다. 이 시나리오에서는 Startup.ConfigureServices 메서드의 기본 스키마를 설정합니다.

    Microsoft.AspNetCore.Server.IISIntegration의 경우 기본 체계를 IISDefaults.AuthenticationScheme으로 설정합니다.

    using Microsoft.AspNetCore.Server.IISIntegration;
    
    services.AddAuthentication(IISDefaults.AuthenticationScheme);
    

    Microsoft.AspNetCore.Server.HttpSys의 경우 기본 체계를 HttpSysDefaults.AuthenticationScheme으로 설정합니다.

    using Microsoft.AspNetCore.Server.HttpSys;
    
    services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
    

    기본 체계를 설정하지 않으면 권한 부여(챌린지) 요청이 다음 예외와 함께 작동하지 않습니다.

    System.InvalidOperationException: authenticationScheme이 지정되지 않았으며 DefaultChallengeScheme을 찾을 수 없습니다.

자세한 내용은 ASP.NET Core에서 Windows 인증 구성을 참조하세요.

IdentityCookie옵션 인스턴스

2.0 변경의 부작용은 cookie 옵션 인스턴스 대신 명명된 옵션을 사용하는 것으로 전환되는 것입니다. Identitycookie 스키마 이름을 사용자 지정하는 기능이 제거됩니다.

예를 들어 1.x 프로젝트는 생성자 주입을 사용하여 매개 변수를 전달 IdentityCookieOptionsAccountController.cs 합니다.ManageController.cs 외부 cookie 인증 체계는 제공된 인스턴스에서 액세스할 수 있습니다.

public AccountController(
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,
    IOptions<IdentityCookieOptions> identityCookieOptions,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
    _emailSender = emailSender;
    _smsSender = smsSender;
    _logger = loggerFactory.CreateLogger<AccountController>();
}

앞에서 언급한 생성자 삽입은 2.0 프로젝트에서 필요하지 않으며 _externalCookieScheme 필드를 삭제할 수 있습니다.

public AccountController(
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _emailSender = emailSender;
    _smsSender = smsSender;
    _logger = loggerFactory.CreateLogger<AccountController>();
}

1.x 프로젝트는 다음과 같이 _externalCookieScheme 필드를 사용했습니다.

// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);

2.0 프로젝트에서 위의 코드를 다음으로 바꿉니다. IdentityConstants.ExternalScheme 상수는 직접 사용할 수 있습니다.

// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

다음 네임스페이스를 가져와서 새로 추가된 SignOutAsync 호출을 해결합니다.

using Microsoft.AspNetCore.Authentication;

Identity사용자 POCO 탐색 속성 추가

기본 IdentityUser POCO(Plain Old CLR Object)의 EF(Entity Framework) Core 탐색 속성이 제거되었습니다. 1.x 프로젝트에서 이러한 속성을 사용한 경우 수동으로 2.0 프로젝트에 다시 추가합니다.

/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();

/// <summary>
/// Navigation property for the claims this user possesses.
/// </summary>
public virtual ICollection<IdentityUserClaim<int>> Claims { get; } = new List<IdentityUserClaim<int>>();

/// <summary>
/// Navigation property for this users login accounts.
/// </summary>
public virtual ICollection<IdentityUserLogin<int>> Logins { get; } = new List<IdentityUserLogin<int>>();

EF Core 마이그레이션을 실행할 때 중복 외래 키를 방지하려면 IdentityDbContext 클래스의 OnModelCreating 메서드(base.OnModelCreating(); 호출 후)에 다음을 추가합니다.

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    // Customize the ASP.NET Core Identity model and override the defaults if needed.
    // For example, you can rename the ASP.NET Core Identity table names and more.
    // Add your customizations after calling base.OnModelCreating(builder);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Claims)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Logins)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Roles)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);
}

GetExternalAuthenticationSchemes 바꾸기

비동기 버전을 위해 동기 메서드 GetExternalAuthenticationSchemes가 제거되었습니다. 1.x 프로젝트에는 다음 코드가 있습니다.Controllers/ManageController.cs

var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList();

이 메서드도 다음과 같이 표시됩니다.Views/Account/Login.cshtml

@{
    var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList();
    if (loginProviders.Count == 0)
    {
        <div>
            <p>
                There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                for details on setting up this ASP.NET application to support logging in via external services.
            </p>
        </div>
    }
    else
    {
        <form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
            <div>
                <p>
                    @foreach (var provider in loginProviders)
                    {
                        <button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
                    }
                </p>
            </div>
        </form>
    }
}

2.0 프로젝트에서는 GetExternalAuthenticationSchemesAsync 메서드를 사용합니다. 변경 ManageController.cs 내용은 다음 코드와 유사합니다.

var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();
var otherLogins = schemes.Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList();

에서 Login.cshtml루프에 AuthenticationScheme 액세스하는 속성은 foreach 다음과 같이 변경됩니다 Name.

@{
    var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    if (loginProviders.Count == 0)
    {
        <div>
            <p>
                There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                for details on setting up this ASP.NET application to support logging in via external services.
            </p>
        </div>
    }
    else
    {
        <form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
            <div>
                <p>
                    @foreach (var provider in loginProviders)
                    {
                        <button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
                    }
                </p>
            </div>
        </form>
    }
}

ManageLoginsViewModel 속성 변경

ManageLoginsViewModel 개체는 .의 ManageController.cs동작에 ManageLogins 사용됩니다. 1.x 프로젝트에서 개체의 OtherLogins 속성 반환 형식은 IList<AuthenticationDescription>입니다. 이 반환 형식을 사용하려면 Microsoft.AspNetCore.Http.Authentication을 가져와야 합니다.

using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore1App.Models.ManageViewModels
{
    public class ManageLoginsViewModel
    {
        public IList<UserLoginInfo> CurrentLogins { get; set; }

        public IList<AuthenticationDescription> OtherLogins { get; set; }
    }
}

2.0 프로젝트에서 반환 형식은 IList<AuthenticationScheme>으로 변경합니다. 이 새 반환 형식을 사용하려면 Microsoft.AspNetCore.Http.Authentication 가져오기를 Microsoft.AspNetCore.Authentication 가져오기로 대체해야 합니다.

using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore2App.Models.ManageViewModels
{
    public class ManageLoginsViewModel
    {
        public IList<UserLoginInfo> CurrentLogins { get; set; }

        public IList<AuthenticationScheme> OtherLogins { get; set; }
    }
}

추가 리소스

자세한 내용은 GitHub의 인증 2.0 토론 문제를 참조하세요.