다음을 통해 공유


ASP.NET Web API 2의 인증 필터

작성자: Mike Wasson

인증 필터는 HTTP 요청을 인증하는 구성 요소입니다. Web API 2 및 MVC 5는 모두 인증 필터를 지원하지만, 주로 필터 인터페이스에 대한 명명 규칙에서 약간 다릅니다. 이 항목에서는 Web API 인증 필터에 대해 설명합니다.

인증 필터를 사용하면 개별 컨트롤러 또는 작업에 대한 인증 체계를 설정할 수 있습니다. 이렇게 하면 앱이 서로 다른 HTTP 리소스에 대해 서로 다른 인증 메커니즘을 지원할 수 있습니다.

이 문서에서는 의 기본 인증 샘플 https://github.com/aspnet/samples에서 코드를 보여 드리겠습니다. 이 샘플에서는 HTTP 기본 액세스 인증 체계(RFC 2617)를 구현하는 인증 필터를 보여 줍니다. 필터는 라는 IdentityBasicAuthenticationAttribute클래스에서 구현됩니다. 샘플의 모든 코드를 표시하지는 않으며 인증 필터를 작성하는 방법을 보여 주는 부분만 표시합니다.

인증 필터 설정

다른 필터와 마찬가지로 인증 필터는 컨트롤러별, 작업별 또는 전역적으로 모든 Web API 컨트롤러에 적용할 수 있습니다.

컨트롤러에 인증 필터를 적용하려면 컨트롤러 클래스를 필터 특성으로 데코레이트합니다. 다음 코드는 컨트롤러의 모든 작업에 대해 기본 인증을 사용하도록 설정하는 컨트롤러 클래스의 필터를 설정합니다 [IdentityBasicAuthentication] .

[IdentityBasicAuthentication] // Enable Basic authentication for this controller.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }
    public IHttpActionResult Post() { . . . }
}

필터를 하나의 작업에 적용하려면 필터를 사용하여 작업을 데코레이트합니다. 다음 코드는 컨트롤러의 Post 메서드에 대한 필터를 설정합니다[IdentityBasicAuthentication].

[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }

    [IdentityBasicAuthentication] // Enable Basic authentication for this action.
    public IHttpActionResult Post() { . . . }
}

필터를 모든 Web API 컨트롤러에 적용하려면 GlobalConfiguration.Filters에 추가합니다.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new IdentityBasicAuthenticationAttribute());

        // Other configuration code not shown...
    }
}

Web API 인증 필터 구현

Web API에서 인증 필터는 System.Web.Http.Filters.IAuthenticationFilter 인터페이스를 구현합니다. 또한 특성으로 적용하려면 System.Attribute에서 상속해야 합니다.

IAuthenticationFilter 인터페이스에는 두 가지 메서드가 있습니다.

  • AuthenticateAsync 는 요청의 자격 증명(있는 경우)의 유효성을 검사하여 요청을 인증합니다.
  • ChallengeAsync 는 필요한 경우 HTTP 응답에 인증 챌린지를 추가합니다.

이러한 메서드는 RFC 2612 및 RFC 2617 에 정의된 인증 흐름 해당합니다.

  1. 클라이언트는 권한 부여 헤더에서 자격 증명을 보냅니다. 이는 일반적으로 클라이언트가 서버로부터 401(권한 없음) 응답을 받은 후에 발생합니다. 그러나 클라이언트는 401을 받은 후뿐만 아니라 모든 요청으로 자격 증명을 보낼 수 있습니다.
  2. 서버에서 자격 증명을 수락하지 않으면 401(권한 없음) 응답을 반환합니다. 응답에는 하나 이상의 문제가 포함된 Www-Authenticate 헤더가 포함됩니다. 각 챌린지는 서버에서 인식하는 인증 체계를 지정합니다.

서버는 익명 요청에서 401을 반환할 수도 있습니다. 실제로 인증 프로세스가 시작되는 방법은 일반적으로 다음과 같습니다.

  1. 클라이언트는 익명 요청을 보냅니다.
  2. 서버는 401을 반환합니다.
  3. 클라이언트는 자격 증명을 사용하여 요청을 다시 보냅니다.

이 흐름에는 인증권한 부여 단계가 모두 포함됩니다.

  • 인증은 클라이언트의 ID를 증명합니다.
  • 권한 부여는 클라이언트가 특정 리소스에 액세스할 수 있는지 여부를 결정합니다.

Web API에서 인증 필터는 인증을 처리하지만 권한 부여는 처리하지 않습니다. 권한 부여는 권한 부여 필터 또는 컨트롤러 작업 내에서 수행해야 합니다.

Web API 2 파이프라인의 흐름은 다음과 같습니다.

  1. 작업을 호출하기 전에 Web API는 해당 작업에 대한 인증 필터 목록을 만듭니다. 여기에는 작업 scope, 컨트롤러 scope 및 전역 scope 있는 필터가 포함됩니다.
  2. Web API는 목록의 모든 필터에서 AuthenticateAsync 를 호출합니다. 각 필터는 요청에서 자격 증명의 유효성을 검사할 수 있습니다. 필터가 자격 증명의 유효성을 성공적으로 검사하는 경우 필터는 IPrincipal 을 만들고 요청에 연결합니다. 필터는 이 시점에서 오류를 트리거할 수도 있습니다. 이 경우 나머지 파이프라인은 실행되지 않습니다.
  3. 오류가 없다고 가정하면 요청은 파이프라인의 나머지 부분을 통과합니다.
  4. 마지막으로 Web API는 모든 인증 필터의 ChallengeAsync 메서드를 호출합니다. 필터는 필요한 경우 이 메서드를 사용하여 응답에 챌린지를 추가합니다. 일반적으로 401 오류에 대한 응답으로 발생하는 (항상 그런 것은 아님)

다음 다이어그램에서는 두 가지 가능한 사례를 보여 줍니다. 첫 번째 인증 필터는 요청을 성공적으로 인증하고, 권한 부여 필터는 요청에 권한을 부여하고, 컨트롤러 작업은 200(OK)을 반환합니다.

성공적인 인증 다이어그램

두 번째 예제에서 인증 필터는 요청을 인증하지만 권한 부여 필터는 401(권한 없음)을 반환합니다. 이 경우 컨트롤러 작업이 호출되지 않습니다. 인증 필터는 응답에 Www-Authenticate 헤더를 추가합니다.

권한 없는 인증 다이어그램

다른 조합이 가능합니다. 예를 들어 컨트롤러 작업에서 익명 요청을 허용하는 경우 인증 필터가 있지만 권한 부여는 없을 수 있습니다.

AuthenticateAsync 메서드 구현

AuthenticateAsync 메서드는 요청을 인증하려고 시도합니다. 메서드 서명은 다음과 같습니다.

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

AuthenticateAsync 메서드는 다음 중 하나를 수행해야 합니다.

  1. 아무것도 (no-op).
  2. IPrincipal을 만들고 요청에 따라 설정합니다.
  3. 오류 결과를 설정합니다.

옵션(1)은 요청에 필터가 이해하는 자격 증명이 없음을 의미합니다. 옵션(2)은 필터가 요청을 성공적으로 인증한 것을 의미합니다. 옵션(3)은 요청에 잘못된 자격 증명(예: 잘못된 암호)이 있어 오류 응답을 트리거한다는 것을 의미합니다.

다음은 AuthenticateAsync를 구현하기 위한 일반적인 개요입니다.

  1. 요청에서 자격 증명을 찾습니다.
  2. 자격 증명이 없으면 아무 작업도 수행하지 않고 (no-op)을 반환합니다.
  3. 자격 증명이 있지만 필터가 인증 체계를 인식하지 못하는 경우 아무 작업도 수행하지 않고 (no-op)을 반환합니다. 파이프라인의 다른 필터는 스키마를 이해할 수 있습니다.
  4. 필터에서 이해하는 자격 증명이 있는 경우 인증을 시도합니다.
  5. 자격 증명이 잘못된 경우 를 설정 context.ErrorResult하여 401을 반환합니다.
  6. 자격 증명이 유효한 경우 IPrincipal 을 만들고 를 설정합니다 context.Principal.

다음 코드는 기본 인증 샘플의 AuthenticateAsync 메서드를 보여줍니다. 주석은 각 단계를 나타냅니다. 코드에는 자격 증명이 없는 권한 부여 헤더, 잘못된 자격 증명 및 잘못된 사용자 이름/암호와 같은 여러 유형의 오류가 표시됩니다.

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
    // 1. Look for credentials in the request.
    HttpRequestMessage request = context.Request;
    AuthenticationHeaderValue authorization = request.Headers.Authorization;

    // 2. If there are no credentials, do nothing.
    if (authorization == null)
    {
        return;
    }

    // 3. If there are credentials but the filter does not recognize the 
    //    authentication scheme, do nothing.
    if (authorization.Scheme != "Basic")
    {
        return;
    }

    // 4. If there are credentials that the filter understands, try to validate them.
    // 5. If the credentials are bad, set the error result.
    if (String.IsNullOrEmpty(authorization.Parameter))
    {
        context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
        return;
    }

    Tuple<string, string> userNameAndPassword = ExtractUserNameAndPassword(authorization.Parameter);
    if (userNameAndPassword == null)
    {
        context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
    }

    string userName = userNameAndPassword.Item1;
    string password = userNameAndPassword.Item2;

    IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
    if (principal == null)
    {
        context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
    }

    // 6. If the credentials are valid, set principal.
    else
    {
        context.Principal = principal;
    }

}

오류 결과 설정

자격 증명이 잘못된 경우 필터는 오류 응답을 만드는 IHttpActionResult로 설정 context.ErrorResult 해야 합니다. IHttpActionResult에 대한 자세한 내용은 Web API 2의 작업 결과를 참조하세요.

기본 인증 샘플에는 이 용도에 AuthenticationFailureResult 적합한 클래스가 포함되어 있습니다.

public class AuthenticationFailureResult : IHttpActionResult
{
    public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
    {
        ReasonPhrase = reasonPhrase;
        Request = request;
    }

    public string ReasonPhrase { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    private HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        response.RequestMessage = Request;
        response.ReasonPhrase = ReasonPhrase;
        return response;
    }
}

ChallengeAsync 구현

ChallengeAsync 메서드의 목적은 필요한 경우 응답에 인증 챌린지를 추가하는 것입니다. 메서드 서명은 다음과 같습니다.

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

메서드는 요청 파이프라인의 모든 인증 필터에서 호출됩니다.

HTTP 응답을 만들기 전과 컨트롤러 작업이 실행되기 전에ChallengeAsync가 호출된다는 것을 이해하는 것이 중요합니다. ChallengeAsync가 호출 context.Result 되면 에는 나중에 HTTP 응답을 만드는 데 사용되는 IHttpActionResult가 포함됩니다. 따라서 ChallengeAsync 가 호출되면 HTTP 응답에 대해 아직 알 수 없습니다. ChallengeAsync 메서드는 의 원래 값을 context.ResultIHttpActionResult로 바꿔야 합니다. 이 IHttpActionResult 는 원래 context.Result를 래핑해야 합니다.

ChallengeAsync 다이어그램

원래 IHttpActionResult내부 결과라고 하고 새 IHttpActionResult를 외부 결과라고 합니다. 외부 결과는 다음을 수행해야 합니다.

  1. 내부 결과를 호출하여 HTTP 응답을 만듭니다.
  2. 응답을 검사합니다.
  3. 필요한 경우 응답에 인증 챌린지를 추가합니다.

다음 예제는 기본 인증 샘플에서 가져옵니다. 외부 결과에 대한 IHttpActionResult 를 정의합니다.

public class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
    public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
    {
        Challenge = challenge;
        InnerResult = innerResult;
    }

    public AuthenticationHeaderValue Challenge { get; private set; }

    public IHttpActionResult InnerResult { get; private set; }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            // Only add one challenge per authentication scheme.
            if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
            {
                response.Headers.WwwAuthenticate.Add(Challenge);
            }
        }

        return response;
    }
}

속성은 InnerResult 내부 IHttpActionResult를 보유합니다. 속성은 Challenge Www-Authentication 헤더를 나타냅니다. ExecuteAsync는 먼저 를 호출 InnerResult.ExecuteAsync 하여 HTTP 응답을 만든 다음 필요한 경우 챌린지를 추가합니다.

챌린지를 추가하기 전에 응답 코드를 확인합니다. 대부분의 인증 체계는 여기에 표시된 것처럼 응답이 401인 경우에만 챌린지를 추가합니다. 그러나 일부 인증 체계는 성공 응답에 챌린지를 추가합니다. 예를 들어 협상 (RFC 4559)을 참조하세요.

클래스를 AddChallengeOnUnauthorizedResult 고려할 때 ChallengeAsync 의 실제 코드는 간단합니다. 결과를 만들어 에 연결하기만 하면 됩니다 context.Result.

public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
    var challenge = new AuthenticationHeaderValue("Basic");
    context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
    return Task.FromResult(0);
}

참고: 기본 인증 샘플은 확장 메서드에 배치하여 이 논리를 약간 추상화합니다.

인증 필터와 Host-Level 인증 결합

"호스트 수준 인증"은 요청이 Web API 프레임워크에 도달하기 전에 호스트(예: IIS)에서 수행하는 인증입니다.

대부분의 경우 애플리케이션의 나머지 부분에 대해 호스트 수준 인증을 사용하도록 설정하지만 Web API 컨트롤러에 대해 사용하지 않도록 설정할 수 있습니다. 예를 들어 일반적인 시나리오는 호스트 수준에서 Forms 인증을 사용하도록 설정하지만 Web API에 토큰 기반 인증을 사용하는 것입니다.

Web API 파이프라인 내에서 호스트 수준 인증을 사용하지 않도록 설정하려면 구성에서 를 호출 config.SuppressHostPrincipal() 합니다. 이로 인해 Web API는 Web API 파이프라인을 입력하는 모든 요청에서 IPrincipal 을 제거합니다. 효과적으로 요청을 "인증 해제"합니다.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SuppressHostPrincipal();

        // Other configuration code not shown...
    }
}

추가 리소스

ASP.NET Web API 보안 필터(MSDN Magazine)