작성자: 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() { . . . }
}
필터를 하나의 작업에 적용하려면 필터를 사용하여 작업을 데코레이트합니다. 다음 코드는 컨트롤러의 [IdentityBasicAuthentication] 메서드에 필터를 설정합니다Post.
[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 에 정의된 인증 흐름 에 해당합니다.
- 클라이언트는 권한 부여 헤더에서 자격 증명을 보냅니다. 이는 일반적으로 클라이언트가 서버에서 401(권한 없음) 응답을 받은 후에 발생합니다. 그러나 클라이언트는 401을 받은 후가 아니라 모든 요청으로 자격 증명을 보낼 수 있습니다.
- 서버가 자격 증명을 수락하지 않으면 401(권한 없음) 응답을 반환합니다. 응답에는 하나 이상의 챌린지가 포함된 Www-Authenticate 헤더가 포함됩니다. 각 챌린지는 서버에서 인식하는 인증 체계를 지정합니다.
서버는 익명 요청에서 401을 반환할 수도 있습니다. 실제로 인증 프로세스가 시작되는 방법은 일반적으로 다음과 같습니다.
- 클라이언트는 익명 요청을 보냅니다.
- 서버는 401을 반환합니다.
- 클라이언트는 자격 증명을 사용하여 요청을 다시 보냅니다.
이 흐름에는 인증 및 권한 부여 단계가 모두 포함됩니다.
- 인증은 클라이언트의 ID를 증명합니다.
- 권한 부여는 클라이언트가 특정 리소스에 액세스할 수 있는지 여부를 결정합니다.
Web API에서 인증 필터는 인증을 처리하지만 권한 부여는 처리하지 않습니다. 권한 부여는 권한 부여 필터 또는 컨트롤러 작업 내에서 수행해야 합니다.
Web API 2 파이프라인의 흐름은 다음과 같습니다.
- 작업을 호출하기 전에 Web API는 해당 작업에 대한 인증 필터 목록을 만듭니다. 여기에는 작업 범위, 컨트롤러 범위 및 전역 범위가 있는 필터가 포함됩니다.
- Web API는 목록의 모든 필터에서 AuthenticateAsync 를 호출합니다. 각 필터는 요청에서 자격 증명의 유효성을 검사할 수 있습니다. 필터가 자격 증명의 유효성을 성공적으로 검사하는 경우 필터는 IPrincipal 을 만들고 요청에 연결합니다. 필터는 이 시점에서 오류를 트리거할 수도 있습니다. 그렇다면 나머지 파이프라인은 실행되지 않습니다.
- 오류가 없다고 가정하면 요청은 파이프라인의 나머지 부분을 통과합니다.
- 마지막으로 Web API는 모든 인증 필터의 ChallengeAsync 메서드를 호출합니다. 필요한 경우 필터는 이 메서드를 사용하여 응답에 챌린지를 추가합니다. 일반적으로(항상 그런 것은 아님) 401 오류에 대한 응답으로 발생합니다.
다음 다이어그램에서는 두 가지 가능한 사례를 보여 줍니다. 첫 번째 인증 필터는 요청을 성공적으로 인증하고, 권한 부여 필터는 요청에 권한을 부여하고, 컨트롤러 작업은 200(확인)을 반환합니다.
두 번째 예제에서 인증 필터는 요청을 인증하지만 권한 부여 필터는 401(권한 없음)을 반환합니다. 이 경우 컨트롤러 동작이 호출되지 않습니다. 인증 필터는 응답에 Www-Authenticate 헤더를 추가합니다.
다른 조합이 가능합니다. 예를 들어 컨트롤러 작업에서 익명 요청을 허용하는 경우 인증 필터가 있지만 권한 부여는 없을 수 있습니다.
AuthenticateAsync 메서드 구현
AuthenticateAsync 메서드는 요청을 인증하려고 시도합니다. 메서드 서명은 다음과 같습니다.
Task AuthenticateAsync(
HttpAuthenticationContext context,
CancellationToken cancellationToken
)
AuthenticateAsync 메서드는 다음 중 하나를 수행해야 합니다.
- 없음(연산 없음).
- IPrincipal을 만들고 요청에 설정합니다.
- 오류 결과를 설정합니다.
옵션(1)은 요청에 필터가 이해하는 자격 증명이 없음을 의미합니다. 옵션(2)은 필터가 요청을 성공적으로 인증되었음을 의미합니다. 옵션(3)은 요청에 잘못된 자격 증명(예: 잘못된 암호)이 있어 오류 응답을 트리거한다는 것을 의미합니다.
다음은 AuthenticateAsync를 구현하기 위한 일반적인 개요입니다.
- 요청에서 자격 증명을 확인합니다.
- 자격 증명이 없으면 아무 작업도 수행하지 않고 반환합니다(no-op).
- 자격 증명이 있지만 필터가 인증 체계를 인식하지 못하는 경우 아무 작업도 수행하지 않고 반환(no-op)합니다. 파이프라인의 다른 필터는 스키마를 이해할 수 있습니다.
- 필터에서 이해하는 자격 증명이 있는 경우 인증을 시도합니다.
- 자격 증명이 잘못된 경우
context.ErrorResult를 설정하여 401을 반환합니다. - 자격 증명이 유효한 경우 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;
}
}
오류 결과 설정
자격 증명이 잘못된 경우, 필터는 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.Result 새 IHttpActionResult로 바꿔야 합니다. 이 IHttpActionResult 는 원본 context.Result을 래핑해야 합니다.
원래 IHttpActionResult를 내부 결과라고 하고 새 IHttpActionResult를 외부 결과라고 합니다. 외부 결과는 다음을 수행해야 합니다.
- 내부 결과를 호출하여 HTTP 응답을 만듭니다.
- 응답을 검사합니다.
- 필요한 경우 응답에 인증 챌린지를 추가합니다.
다음 예제는 기본 인증 샘플에서 가져온 것입니다. 외부 결과에 대한 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는 먼저 HTTP 응답을 만들기 위해 호출 InnerResult.ExecuteAsync 한 다음 필요한 경우 챌린지를 추가합니다.
챌린지를 추가하기 전에 응답 코드를 확인합니다. 대부분의 인증 체계는 여기에 표시된 것처럼 응답이 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)