인증: ASP.NET Core Identity 없이 Cookie 미들웨어 사용하기

등록일시: 2017-03-20 08:00,  수정일시: 2017-05-31 06:31
조회수: 8,013
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 ASP.NET Core Identity 없이 쿠키 미들웨어만 독립적으로 사용하는 방법을 살펴봅니다.

ASP.NET Core는 사용자의 신원 계정을 암호화 된 쿠키에 직렬화해서 저장한 다음, 이어지는 요청 시 해당 쿠키의 유효성을 검사하고, 신원 계정을 다시 생성해서 HttpContextUser 속성에 할당해주는 쿠키 미들웨어를 제공합니다. 자체적으로 로그인 화면 및 사용자 데이터베이스를 구현하고자 한다면 ASP.NET Core Identity 없이 쿠키 미들웨어만 독립적으로 사용할 수 있습니다.

쿠키 미들웨어 추가 및 구성하기

먼저 해야 할 일은 응용 프로그램에 쿠키 미들웨어를 추가하는 것입니다. NuGet으로 Microsoft.AspNetCore.Authentication.Cookies 패키지를 추가합니다. 그런 다음, Startup.cs 파일의 Configure 메서드에 작성되어 있는 app.UseMvc() 구문 앞에 다음 코드를 추가합니다:

app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
    AuthenticationScheme = "MyCookieMiddlewareInstance",
    LoginPath = new PathString("/Account/Unauthorized/"),
    AccessDeniedPath = new PathString("/Account/Forbidden/"),
    AutomaticAuthenticate = true,
    AutomaticChallenge = true
});

이 코드 스니핏은 몇 가지 옵션들을 구성하고 있습니다:

  • AuthenticationScheme - 미들웨어를 식별하는 값입니다. 미들웨어의 인스턴스가 여러 개 존재할 때, 그 중 한 인스턴스에만 제한적으로 권한을 부여하고자 하는 경우 유용합니다.

  • LoginPath - 사용자가 리소스에 접근하려고 시도했지만 인증되지 않은 경우에 요청이 재지정 될 상대 경로입니다.

  • AccessDeniedPath - 사용자가 리소스에 접근하려고 시도했지만 해당 리소스에 관한 권한 부여 정책을 통과하지 못한 경우에 요청이 재지정 될 상대 경로입니다.

  • AutomaticAuthenticate - 이 플래그는 미들웨어가 모든 요청을 대상으로 자동으로 실행되어 기존에 생성된 모든 직렬화된 신원 계정의 유효성을 검사하고 재생성해야 하는지 여부를 지정합니다.

  • AutomaticChallenge - 이 플래그는 권한 부여 실패 시 미들웨어가 브라우저를 LoginPath 또는 AccessDeniedPath로 재지정해야 하는지 여부를 지정합니다.

이 밖에도 미들웨어가 생성하는 모든 클레임의 발급자를 설정하거나, 미들웨어가 생성하는 쿠키의 이름이나 도메인을 지정하고, 쿠키의 다양한 보안 속성들을 설정하는 옵션들도 제공됩니다. 기본적으로 쿠키 미들웨어는 클라이언트 측 JavaScript에서 쿠키에 접근하지 못하도록 HTTPONLY 속성을 설정하며, HTTPS를 통해서 요청이 전달된 경우 HTTPS 요청을 대상으로만 쿠키를 제한하는 등 적절한 보안 옵션이 설정된 쿠키를 생성합니다.

사용자 정보를 담고 있는 쿠키를 생성하기 위해서는 먼저 쿠키에 직렬화시킨 정보를 담고 있는 ClaimsPrincipal 개체를 만들어야 합니다. 컨트롤러 메서드 내부에서 적절한 ClaimsPrincipal 개체를 얻고 다음과 같이 호출합니다.

await HttpContext.Authentication.SignInAsync("MyCookieMiddlewareInstance", principal);

그러면 암호화 된 쿠키가 만들어져서 현재 응답에 추가됩니다. 이처럼 SignInAsync 메서드를 호출할 때는 구성 과정 중에 지정했던 AuthenticationScheme 값을 함께 전달해야 합니다.

내부적으로 암호화에는 ASP.NET의 데이터 보호 시스템이 사용됩니다. 만약 응용 프로그램을 여러 머신에서 로드 밸런싱을 통해서 호스팅하거나 웹 팜을 사용한다면, 동일한 키 링 및 응용 프로그램 식별자를 사용하도록 데이터 보호 시스템을 구성해야 합니다.

로그아웃

현재 사용자를 로그아웃시키고 쿠키를 삭제하려면 컨트롤러에서 다음과 같이 호출하면 됩니다.

await HttpContext.Authentication.SignOutAsync("MyCookieMiddlewareInstance");

백-엔드 변경 시 대응하기

주의

신원 계정을 담고 있는 쿠키가 생성되고 나면 이 쿠키가 신원에 관한 유일한 판단 기준이 됩니다. 설령 백-엔드 시스템에서 사용자를 비활성화시킨다고 하더라도 쿠키 미들웨어는 그 사실을 알 수 없으며 쿠키가 유효한 한 사용자는 로그인 상태를 그대로 유지합니다.

쿠키 인증 미들웨어는 옵션 클래스의 Events 속성을 통해서 일련의 이벤트들을 제공해줍니다. 그 중, OnValidatePrincipal 이벤트를 활용하면 쿠키에 저장된 신원을 대상으로 수행되는 유효성 검사를 가로채고 재정의 할 수 있습니다.

우선 사용자 데이터베이스에 LastChanged라는 컬럼이 존재한다고 가정해보겠습니다. 만약 사용자 데이터베이스가 변경될 경우 기존에 생성된 쿠키를 무효화시키고자 한다면, 가장 먼저 해야 할 일은 쿠키를 생성할 때 현재 값을 담고 있는 LastChanged 클레임을 추가하는 것입니다. 또한, 데이터베이스가 변경될 때 LastChanged 컬럼의 값도 항상 함께 변경해야 합니다.

그런 다음, 다음과 같은 시그니처를 갖고 있는 메서드를 작성해서 OnValidatePrincipal 이벤트에 대한 재정의를 구현합니다:

Task ValidateAsync(CookieValidatePrincipalContext context);

예를 들어서, ASP.NET Core Identity에서는 이 검사를 SecurityStampValidator의 일부로 구현하고 있습니다. 간단히 예제를 구현해보면 아마도 다음과 비슷할 것입니다:

public static class LastChangedValidator
{
    public static async Task ValidateAsync(CookieValidatePrincipalContext context)
    {
        // Pull database from registered DI services.
        var userRepository = context.HttpContext.RequestServices.GetRequiredService<IUserRepository>();
        var userPrincipal = context.Principal;

        // Look for the last changed claim.
        string lastChanged;
        lastChanged = (from c in userPrincipal.Claims
                       where c.Type == "LastUpdated"
                       select c.Value).FirstOrDefault();

        if (string.IsNullOrEmpty(lastChanged) ||
            !userRepository.ValidateLastChanged(userPrincipal, lastChanged))
        {
            context.RejectPrincipal();
            await context.HttpContext.Authentication.SignOutAsync("MyCookieMiddlewareInstance");
        }
    }
}

그런 다음, 쿠키 미들웨어를 구성할 때, 구현한 이벤트 헨들러 메서드를 연결합니다.

app.UseCookieAuthentication(options =>
{
    options.Events = new CookieAuthenticationEvents
    {
        // Set other options
        OnValidatePrincipal = LastChangedValidator.ValidateAsync
    };
});

만약 사용자의 이름이 변경된 경우처럼 보안에 어떠한 영향도 미치지 않는다고 판단될 때는, 지금처럼 사용자 신원 계정을 제거하는 대신 context.ReplacePrincipal()을 호출하고 context.ShouldRenew 플래그를 true로 설정해서 갱신할 수도 있습니다.

CookieAuthenticationOptions 클래스는 생성되는 쿠키를 세밀하게 제어할 수 있는 다양한 구성 옵션들을 제공합니다.

  • ClaimsIssuer - 미들웨어에 의해서 생성되는 모든 클레임의 Issuer 속성에 사용될 발급자를 지정합니다.

  • CookieDomain - 쿠키가 제공될 도메인 이름입니다. 기본값은 요청이 전송된 호스트 명으로, 브라우저에서는 호스트 명이 일치하는 쿠키에만 접근할 수 있습니다. 필요에 따라 도메인 상의 모든 호스트에서 쿠키를 사용할 수 있도록 이 값을 조정할 수 있습니다. 가령, 쿠키 도메인을 .contoso.com으로 설정하면 contoso.com, www.contoso.com, staging.www.contoso.com 등에서 쿠키를 사용할 수 있습니다.

  • CookieHttpOnly - 서버에서만 쿠키에 접근할 수 있는지 여부를 나타내는 플래그입니다. 기본값은 true입니다. 이 값을 변경하면 응용 프로그램에 교차 사이트 스크립팅(Cross Site Scripting) 버그가 존재할 경우, 쿠키가 도용될 수도 있습니다.

  • CookiePath - 이 옵션을 사용하면 동일한 호스트 명에서 실행되는 응용 프로그램들을 격리시킬 수 있습니다. 예를 들어, /app1에서 실행되는 응용 프로그램이 존재하고, 발급된 쿠키가 해당 응용 프로그램에만 전송되도록 제한하고 싶다면 CookiePath 속성을 /app1으로 설정합니다. 그러면 /app1이나 그 하위 URL 들에 대한 요청에서만 쿠키를 사용할 수 있게 됩니다.

  • CookieSecure - 생성된 쿠키를 HTTPS, HTTP 및 HTTPS, 또는 요청과 동일한 프로토콜만으로 제한해야 하는지 여부를 나타내는 플래그입니다. 기본값은 SameAsRequest입니다.

  • ExpireTimeSpan - 쿠키가 만료될 시간 간격을 지정하는 TimeSpan 값입니다. 현재 날짜 및 시간에 이 값이 더해져서 쿠키가 만료될 일시가 결정됩니다.

  • SlidingExpiration - ExpireTimeSpan에 지정된 기간의 절반 이상이 지난 경우, 쿠키 만료 일시가 재설정돼야 하는지 여부를 지정하는 플래그입니다. 새 만료 일시는 현재 일시에 ExpireTimespan 값이 추가된 일시로 연장됩니다. 절대 만료 시간SignInAsync를 호출할 때 AuthenticationProperties 클래스를 이용해서 설정할 수 있습니다. 절대 만료 시간을 설정하면 인증 쿠기가 유효한 시간을 제한하여 응용 프로그램의 보안을 향상시킬 수 있습니다.

영구 쿠키 및 절대 만료 시간

브라우저의 세션이 끝나도 유지되는 쿠키 만료 시간을 지정하고 싶은 경우가 있습니다. 또는 신원과 해당 신원을 담고 있는 쿠키를 특정 시간에 만료되도록 설정하고 싶은 경우도 있습니다. 이런 작업들은 특정 신원으로 로그인하고 쿠키를 생성하기 위해서 HttpContext.Authentication.SignInAsync 메서드를 호출할 때, AuthenticationProperties 매개변수를 전달해서 구성할 수 있습니다. 이 AuthenticationProperties 클래스는 Microsoft.AspNetCore.Http.Authentication 네임스페이스에 존재합니다.

예를 들어서:

await HttpContext.Authentication.SignInAsync(
    "MyCookieMiddlewareInstance",
    principal,
    new AuthenticationProperties
    {
        IsPersistent = true
    });

이 코드 스니핏은 브라우저가 종료되더라도 그대로 유지되는 신원과 해당 신원에 대한 쿠키를 생성합니다. 기존에 쿠키 옵션을 이용해서 구성한 모든 슬라이딩 시간 만료 설정은 그대로 유지되며, 브라우저가 종료되어 있는 동안 쿠키가 만료될 경우 브라우저가 재시작될 때 쿠키가 제거됩니다.

await HttpContext.Authentication.SignInAsync(
    "MyCookieMiddlewareInstance",
    principal,
    new AuthenticationProperties
    {
        ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
    });

이 코드 스니핏은 20분 간만 유지되는 신원과 해당 신원에 대한 쿠키를 생성합니다. 그리고 기존에 쿠키 옵션을 이용해서 구성한 모든 슬라이딩 시간 만료 설정은 무시됩니다.

ExpiresUtc 속성과 IsPersistent 속성은 상호 배타적이기 때문에 함께 사용할 수는 없습니다.