보안: ASP.NET Web API의 인증(Authentication)과 권한(Authorization)

등록일시: 2014-01-20 08:00,  수정일시: 2014-01-17 08:59
조회수: 17,273
이 문서는 ASP.NET Web API 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

업무에 필요한 Web API의 구현을 마쳤다면, 그 다음으로 해야할 일은 Web API에 대한 접근을 제어하는 작업일 것입니다. 지금부터 일련의 문서들을 통해서 인증되지 않은 사용자로부터 Web API를 보호하기 위한 몇 가지 기능들을 살펴보겠습니다. 이번 시리즈를 통해서 인증과 권한, 모두를 살펴보게 될 것입니다.

  • 인증(Authentication) 은 사용자의 신원을 파악하는 작업입니다. 가령, 엘리스가 자신의 사용자 이름과 비밀번호를 이용해서 로그인을 하면, 서버는 이 비밀번호로 엘리스를 인증합니다.
  • 권한(Authorization) 은 사용자가 어떤 작업을 수행할 수 있는지 여부를 결정하는 작업입니다. 가령, 엘리스는 리소스를 조회할 수 있는 권한은 있지만, 리소스를 작성할 수 있는 권한은 없습니다.

이번 시리즈의 첫 번째 문서인 본문에서는 ASP.NET Web API의 인증과 권한에 대한 전반적인 개념들을 살펴봅니다. 그리고, 나머지 문서들에서는 Web API의 일반적인 인증 시나리오들에 관해서 살펴봅니다.

이번 시리즈를 리뷰하고 중요한 피드백을 제공해주신 분들께 감사를 드립니다:
Rick Anderson, Levi Broderick, Barry Dorrans, Tom Dykstra, Hongmei Ge, David Matson, Daniel Roth, Tim Teebken.

인증(Authentication)

Web API는 호스트에서 인증을 처리한다고 가정합니다. 웹-호스팅 환경의 호스트는 IIS로 HTTP 모듈들을 이용해서 인증을 수행합니다. IIS나 ASP.NET에 내장된 인증 모듈들 중에서 특정 모듈을 사용하도록 프로젝트를 구성하거나, 사용자 지정 인증을 수행하는 HTTP 모듈을 직접 작성할 수도 있습니다.

호스트는 사용자를 인증하면서, 코드가 실행되는 보안 컨텍스트를 나타내는 IPrincipal 개체인 보안 주체(Principal) 를 생성합니다. 그런 다음, 이 보안 주체를 Thread.CurrentPrincipal 속성에 설정해서 현재 쓰레드에 연결합니다. 보안 주체에는 사용자의 정보가 담겨 있는 연관된 Identity 개체가 담겨 있습니다. Identity.IsAuthenticated 속성은 사용자가 인증된 상태에서는 true를 반환하는 반면, 익명 요청 상태에서는 false를 반환합니다. 보안 주체에 관한 보다 자세한 정보들은 Role-Based Security 문서를 참고하시기 바랍니다.

HTTP 메시지 처리기를 이용한 인증

호스트에서 인증을 처리하는 대신, HTTP 메시지 처리기에 인증 로직을 구현할 수도 있습니다. 그런 경우에는, 메시지 처리기에서 HTTP 요청 검토 및 보안 주체 설정 작업을 수행합니다.

그렇다면, 어떤 경우에 메시지 처리기에서 인증을 처리해야 할까요? 여기에는 몇 가지 장단점이 있습니다:

  • HTTP 모듈은 ASP.NET 파이프라인을 거쳐서 이동하는 모든 요청을 볼 수 있습니다. 반면, 메시지 처리기는 Web API로 라우트 된 요청들만 볼 수 있습니다.
  • 메시지 헨들러는 라우트 별로 설정이 가능하므로, 인증 정책 역시 특정 라우트별로만 적용할 수 있습니다.
  • HTTP 모듈은 IIS에 종속적입니다. 그러나, 메시지 처리기는 호스트와는 무관하므로 웹-호스팅 시와 자체-호스팅 시에 모두 사용할 수 있습니다.
  • HTTP 모듈은 IIS 로깅, 감사 등의 대상입니다.
  • HTTP 모듈은 파이프라인의 초반에 실행됩니다. 반면, 메시지 처리기에서 인증을 수행하는 경우에는 처리기가 실행되기 전까지는 보안 주체가 설정되지 않습니다. 뿐만 아니라, 응답이 메시지 처리기를 벗어나는 순간 다시 이전 보안 주체로 돌아가 버립니다.

보편적으로 자체-호스팅을 지원해야 할 필요가 없다면 HTTP 모듈이 가장 좋은 선택입니다. 반면, 자체-호스팅을 지원해야 한다면 메시지 처리기를 고려해볼 필요가 있습니다.

보안 주체 설정하기

응용 프로그램에서 사용자 지정 인증 로직을 수행하는 경우, 두 곳에 보안 주체를 설정해야 합니다:

  • Thread.CurrentPrincipal. 이 속성은 쓰레드의 보안 주체를 설정하는 .NET의 표준적인 방법입니다.
  • HttpContext.Current.User. 이 속성은 ASP.NET에서만 사용할 수 있습니다.

다음 코드는 보안 주체의 설정 방법을 보여줍니다:

private void SetPrincipal(IPrincipal principal)
{
    Thread.CurrentPrincipal = principal;
    if (HttpContext.Current != null)
    {
        HttpContext.Current.User = principal;
    }
}

웹-호스팅 시에는 두 속성 모두 보안 주체를 설정해야 합니다. 그렇지 않으면, 보안 컨텍스트가 서로 달라집니다. 반면, 자체-호스팅 시에는 HttpContext.Current가 null 입니다. 따라서, 호스트 방식과 무관하게 코드를 작성하려면 예제 코드처럼 HttpContext.Current에 할당하기 전에 먼저 null 여부를 검사해야 합니다.

권한(Authorization)

권한은 컨트롤러에 보다 가까운, 파이프라인의 후반부에서 발생합니다. 따라서, 리소스에 대한 접근을 허용할 때 보다 세분화된 선택이 가능합니다.

  • 권한 필터(Authorization Filter) 는 컨트롤러 액션보다 먼저 실행됩니다. 요청에 권한이 없는 경우, 필터가 오류 응답을 반환하고, 액션은 호출되지 않습니다.
  • 컨트롤러 액션 내부에서는 ApiController.User 속성을 이용해서 현재 보안 주체를 얻을 수 있습니다. 가령, 사용자 이름을 기준으로 리소스들의 목록을 필터링한 다음, 해당 사용자에게 속한 리소스들만 반환할 수도 있습니다.

[Authorize] 어트리뷰트 사용하기

Web API는 내장 권한 필터인 AuthorizeAttribute를 제공해줍니다. 이 필터는 사용자의 인증 여부를 파악해줍니다. 만약, 인증되지 않았다면 액션 호출 없이 HTTP 상태 코드 401(인증되지 않음)을 반환합니다.

이 필터는 전역으로, 컨트롤러 수준에, 또는 개별적인 액션 수준에 적용할 수 있습니다.

전역: 모든 Web API 컨트롤러에 대한 접근을 제한하려면 AuthorizeAttribute 필터를 전역 필터 목록에 추가합니다:

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

컨트롤러: 특정 컨트롤러에 대한 접근을 제한하려면 컨트롤러에 어트리뷰트로 필터를 추가합니다:

// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

액션: 특정 액션에 대한 접근을 제한하려면 액션 메서드에 어트리뷰트를 추가합니다:

public class ValuesController : ApiController
{
    public HttpResponseMessage Get() { ... }

    // Require authorization for a specific action.
    [Authorize]
    public HttpResponseMessage Post() { ... }
}

다른 방법으로, 먼저 컨트롤러 전체를 제한한 다음, 특정 액션들만 [AllowAnonymous] 어트리뷰트를 지정해서 익명 접근을 허용 할 수도 있습니다. 다음 예제 코드에서는 Post 메서드는 익명 접근을 제한하고 Get 메서드는 허용하고 있습니다.

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get() { ... }

    public HttpResponseMessage Post() { ... }
}

위의 예제에서는 필터가 제한된 메서드들에 대해 익명 사용자들의 접근만 거부하고 인증된 모든 사용자들의 접근은 허용하고 있습니다. 그러나, 특정 사용자나 특정 역할에 포함된 사용자들만 접근을 허용할 수도 있습니다:

// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}

// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}

Web API 컨트롤러에 대한 AuthorizeAttribute 필터는 System.Web.Http 네임스페이스에 존재합니다. System.Web.Mvc 네임스페이스에도 MVC 컨트롤러에 대응하는 비슷한 필터가 존재하지만 이 필터는 Web API 컨트롤러와는 호환되지 않습니다.

사용자 지정 Authorization 필터

사용자 지정 권한 필터를 작성하려면 다음 형식들 중 하나를 상속받아야 합니다:

  • AuthorizeAttribute. 현재 사용자와 현재 사용자의 역할에 기반한 권한 로직을 수행하려면 이 클래스를 확장합니다.
  • AuthorizationFilterAttribute. 현재 사용자나 역할과 무관하게 동기적 권한 로직을 수행하려면 이 클래스를 확장합니다.
  • IAuthorizationFilter. 비동기적 권한 로직을 수행하려면 이 인터페이스를 구현합니다. 가령, 비동기적인 I/O나 네트워크 호출이 필요한 권한 로직인 경우가 여기에 해당합니다. (만약, 권한 로직이 CPU에 더 많은 영향을 받는다면(CPU-Bound), 비동기 메서드를 작성할 필요가 없으므로 AuthorizationFilterAttribute 클래스를 상속 받는 편이 더 간단합니다.)

다음 다이어그램은 AuthorizeAttribute 클래스의 클래스 계층도를 보여줍니다.

컨트롤러 액션 내부에서의 권한

일부 경우, 요청 자체의 실행은 허용하지만 보안 주체에 따라 동작을 변경하고 싶은 경우도 있습니다. 가령, 사용자의 역할에 따라 반환되는 정보가 바뀌는 경우도 있습니다. 컨트롤러 메서드 내에서는 ApiController.User 속성에서 현재 보안 주체를 가져올 수 있습니다.

public HttpResponseMessage Get()
{
    if (User.IsInRole("Administrators"))
    {
        // ...
    }
}