권한부여: 리소스 기반 권한부여

등록일시: 2017-01-16 08:00,  수정일시: 2017-02-27 11:28
조회수: 3,614
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 리소스 기반 또는 작업 기반의 권한부여 방식과 그에 대한 처리기 작성 방법을 살펴봅니다.

접근하려는 리소스에 따라서 권한부여의 결과가 달라지는 경우도 많습니다. 예를 들어, 문서에 작성자 속성이 존재한다고 가정해보겠습니다. 만약, 문서의 작성자만 문서를 갱신할 수 있어야 한다면, 권한부여를 위한 평가를 수행하기 전에 먼저 문서 저장소에서 리소스, 즉 문서부터 로드해야 합니다. 그러나 어트리뷰트의 평가는 데이터 바인딩 이전에, 그리고 액션 내부에 작성된 코드가 리소스를 로드하기 이전에 수행되기 때문에, 이런 유형의 작업은 Authorize 어트리뷰트로는 처리할 수가 없습니다. 이 경우에는 어트리뷰트 방식의 선언적 권한부여 대신, 코드 내에서 개발자가 권한부여 함수를 호출하는 명령형 권한부여를 사용해야 합니다.

코드를 이용한 권한부여

권한부여는 IAuthorizationService 인터페이스를 구현하는 서비스로 작성되어 서비스 컬렉션에 등록되며, 의존성 주입을 통해서 컨트롤러에서 접근할 수 있습니다.

public class DocumentController : Controller
{
    IAuthorizationService _authorizationService;

    public DocumentController(IAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
    }
}

IAuthorizationService 인터페이스는 두 가지 메서드를 갖고 있는데, 그 중 한 메서드에는 리소스와 정책 이름을 전달할 수 있고, 다른 메서드에는 리소스와 평가할 요구사항들의 목록을 전달할 수 있습니다.

Task<bool> AuthorizeAsync(ClaimsPrincipal user,
        object resource,
        IEnumerable<IAuthorizationRequirement> requirements);
Task<bool> AuthorizeAsync(ClaimsPrincipal user,
        object resource,
        string policyName);

권한부여 서비스를 호출하려면 액션 내부에서 리소스를 로드한 다음, 상황에 맞는 AuthorizeAsync 메서드의 오버로드 버전을 호출하면 됩니다. 가령 다음과 같습니다:

public async Task<IActionResult> Edit(Guid documentId)
{
    Document document = documentRepository.Find(documentId);

    if (document == null)
    {
        return new HttpNotFoundResult();
    }

    if (await authorizationService.AuthorizeAsync(User, document, "EditPolicy"))
    {
        return View(document);
    }
    else
    {
        return new ChallengeResult();
    }
}

리소스 기반 처리기 작성하기

리소스 기반 권한부여를 위한 처리기를 작성하는 방법은 일반적인 요구사항 처리기의 작성 방법과 크게 다르지 않습니다. 먼저 요구사항을 생성한 다음, 기존처럼 요구사항 뿐만 아니라 이번에는 리소스 형식까지 지정해서 해당 요구사항에 대한 처리기를 구현합니다. 예를 들어, Document 리소스를 전달받는 처리기는 다음과 같은 형태를 갖게 됩니다:

public class DocumentAuthorizationHandler : AuthorizationHandler<MyRequirement, Document>
{
    public override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                MyRequirement requirement,
                                                Document resource)
    {
        // Validate the requirement against the resource and identity.

        return Task.CompletedTask;
    }
}

작성한 처리기를 ConfigureServices 메서드에서 등록해야 한다는 점도 잊지 마시기 바랍니다:

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

작업별 요구사항

읽기, 쓰기, 수정, 삭제 같은 작업을 기반으로 권한을 부여해야 하는 경우에는 Microsoft.AspNetCore.Authorization.Infrastructure 네임스페이스의 OperationAuthorizationRequirement 클래스를 활용할 수 있습니다. 기본적으로 제공되는 이 요구사항 클래스를 활용하면 각각 작업들마다 개별적인 클래스를 만드는 대신, 작업 이름을 매개변수로 전달하는 단일 처리기를 만들 수 있습니다. 이 클래스를 사용하기 위해서는 먼저 필요한 작업들의 이름을 정의해야 합니다:

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = "Create" };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement   { Name = "Read" };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = "Update" };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = "Delete" };
}

그러면 다음과 같이 처리기를 구현할 수 있습니다. 이 예제 처리기는 임의의 Document 클래스를 리소스로 사용한다고 가정하고 있습니다:

public class DocumentAuthorizationHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    public override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                OperationAuthorizationRequirement requirement,
                                                Document resource)
    {
        // Validate the operation using the resource, the identity and
        // the Name property value from the requirement.

        return Task.CompletedTask;
    }
}

이 처리기의 코드를 살펴보면 OperationAuthorizationRequirement 클래스를 사용하고 있음을 알 수 있습니다. 그리고 처리기 내부의 코드에서 평가를 수행할 때는 전달된 요구사항의 Name 속성을 반드시 고려해야 합니다.

작업별 리소스 처리기를 호출하기 위해서는 액션에서 AuthorizeAsync 메서드를 호출할 때 다음과 같이 대상 작업을 지정하면 됩니다:

if (await authorizationService.AuthorizeAsync(User, document, Operations.Read))
{
    return View(document);
}
else
{
    return new ChallengeResult();
}

이 예제 코드는 현재 document 인스턴스를 대상으로 사용자가 읽기 작업을 수행할 수 있는지 여부를 확인합니다. 권한부여에 성공하면 해당 문서에 대한 뷰가 반환됩니다. 그러나 권한부여에 실패하면 ChallengeResult가 반환되어, 모든 인증 미들웨어에게 권한부여가 실패했으며 미들웨어에서 401 또는 403 상태 코드를 반환하거나 대화형 브라우저 클라이언트의 경우 사용자를 로그인 페이지로 재지정하는 등과 같은 적절한 응답을 반환할 수 있음을 알려줍니다.