ASP.NET Web API 2'de Kimlik Doğrulama Filtreleri

tarafından Mike Wasson

Kimlik doğrulama filtresi, HTTP isteğinin kimliğini doğrulayan bir bileşendir. Web API 2 ve MVC 5'in her ikisi de kimlik doğrulama filtrelerini destekler, ancak çoğunlukla filtre arabiriminin adlandırma kurallarında biraz farklılık gösterir. Bu konu başlığında Web API kimlik doğrulaması filtreleri açıklanmaktadır.

Kimlik doğrulama filtreleri, tek tek denetleyiciler veya eylemler için bir kimlik doğrulama düzeni ayarlamanıza olanak sağlar. Bu şekilde, uygulamanız farklı HTTP kaynakları için farklı kimlik doğrulama mekanizmalarını destekleyebilir.

Bu makalede, üzerinde https://github.com/aspnet/samples örneğinden kod göstereceğim. Örnekte HTTP Temel Erişim Kimlik Doğrulaması düzenini (RFC 2617) uygulayan bir kimlik doğrulama filtresi gösterilmektedir. Filtre adlı IdentityBasicAuthenticationAttributebir sınıfta uygulanır. Örnekteki tüm kodları göstermeyeceğim, yalnızca kimlik doğrulama filtresinin nasıl yazıldığını gösteren bölümleri göstermeyeceğim.

Kimlik Doğrulama Filtresi Ayarlama

Diğer filtreler gibi kimlik doğrulama filtreleri de denetleyici başına, eylem başına veya genel olarak tüm Web API denetleyicilerine uygulanabilir.

Bir denetleyiciye kimlik doğrulama filtresi uygulamak için, denetleyici sınıfını filter özniteliğiyle süsleyin. Aşağıdaki kod, denetleyicinin [IdentityBasicAuthentication] tüm eylemleri için Temel Kimlik Doğrulaması'nı etkinleştiren bir denetleyici sınıfına filtreyi ayarlar.

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

Filtreyi tek bir eyleme uygulamak için eylemi filtreyle süsleyin. Aşağıdaki kod, denetleyicinin [IdentityBasicAuthentication]Post yöntemindeki filtreyi ayarlar.

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

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

Filtreyi tüm Web API denetleyicilerine uygulamak için GlobalConfiguration.Filters'a ekleyin.

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

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

Web API Kimlik Doğrulama Filtresi Uygulama

Web API'sinde kimlik doğrulama filtreleri System.Web.Http.Filters.IAuthenticationFilter arabirimini uygular. Ayrıca öznitelik olarak uygulanabilmesi için System.Attribute'ten de devralmaları gerekir.

IAuthenticationFilter arabiriminin iki yöntemi vardır:

  • AuthenticateAsync , varsa istekteki kimlik bilgilerini doğrulayarak isteğin kimliğini doğrular.
  • ChallengeAsync gerekirse HTTP yanıtına bir kimlik doğrulama sınaması ekler.

Bu yöntemler RFC 2612 ve RFC 2617'de tanımlanan kimlik doğrulama akışına karşılık gelir:

  1. İstemci kimlik bilgilerini Yetkilendirme üst bilgisinde gönderir. Bu durum genellikle istemci sunucudan 401 (Yetkisiz) yanıtı aldıktan sonra gerçekleşir. Ancak, bir istemci yalnızca 401 aldıktan sonra değil, herhangi bir istekle kimlik bilgileri gönderebilir.
  2. Sunucu kimlik bilgilerini kabul etmiyorsa, bir 401 (Yetkisiz) yanıtı döndürür. Yanıt, bir veya daha fazla zorluk içeren bir Www-Authenticate üst bilgisi içerir. Her sınama, sunucu tarafından tanınan bir kimlik doğrulama şeması belirtir.

Sunucu ayrıca anonim bir istekten 401 döndürebilir. Aslında, kimlik doğrulama işlemi genellikle böyle başlatılır:

  1. İstemci anonim bir istek gönderir.
  2. Sunucu 401 döndürür.
  3. İstemciler isteği kimlik bilgileriyle yeniden gönderebilir.

Bu akış hem kimlik doğrulama hem de yetkilendirme adımlarını içerir.

  • Kimlik doğrulaması, istemcinin kimliğini kanıtlar.
  • Yetkilendirme, istemcinin belirli bir kaynağa erişip erişemeyeceğini belirler.

Web API'sinde kimlik doğrulama filtreleri kimlik doğrulamasını işler ancak yetkilendirmeyi işlemez. Yetkilendirme, yetkilendirme filtresiyle veya denetleyici eyleminin içinde yapılmalıdır.

Web API 2 işlem hattındaki akış aşağıda verilmiştir:

  1. Bir eylemi çağırmadan önce, Web API'si bu eylem için kimlik doğrulama filtrelerinin listesini oluşturur. Buna eylem kapsamı, denetleyici kapsamı ve genel kapsama sahip filtreler dahildir.
  2. Web API'si listedeki her filtrede AuthenticateAsync'i çağırır. Her filtre istekteki kimlik bilgilerini doğrulayabilir. Herhangi bir filtre kimlik bilgilerini başarıyla doğrularsa, filtre bir IPrincipal oluşturur ve bunu isteğe ekler. Bu noktada bir filtre de bir hata tetikleyebilir. Bu durumda işlem hattının geri kalanı çalışmaz.
  3. Hata olmadığını varsayarsak, istek işlem hattının geri kalanından geçer.
  4. Son olarak, Web API'si her kimlik doğrulama filtresinin ChallengeAsync yöntemini çağırır. Filtreler gerekirse yanıta bir sınama eklemek için bu yöntemi kullanır. Genellikle (ancak her zaman değil) bir 401 hatasına yanıt olarak gerçekleşir.

Aşağıdaki diyagramlarda iki olası durum gösterilmektedir. İlkinde kimlik doğrulama filtresi isteğin kimliğini başarıyla doğrular, yetkilendirme filtresi isteği yetkilendirir ve denetleyici eylemi 200 (Tamam) döndürür.

Başarılı kimlik doğrulaması diyagramı

İkinci örnekte, kimlik doğrulama filtresi isteğin kimliğini doğrular, ancak yetkilendirme filtresi 401 (Yetkisiz) döndürür. Bu durumda denetleyici eylemi çağrılmıyor. Kimlik doğrulama filtresi yanıta bir Www-Authenticate üst bilgisi ekler.

Yetkisiz kimlik doğrulaması diyagramı

Diğer birleşimler de mümkündür; örneğin, denetleyici eylemi anonim isteklere izin veriyorsa, bir kimlik doğrulama filtreniz olabilir ancak yetkilendirmeniz olmayabilir.

AuthenticateAsync Yöntemini Uygulama

AuthenticateAsync yöntemi isteğin kimliğini doğrulamayı dener. Yöntem imzası aşağıdadır:

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

AuthenticateAsync yöntemi aşağıdakilerden birini yapmalıdır:

  1. Hiçbir şey (işlemsizlik).
  2. Bir IPrincipal oluşturun ve isteğe ayarlayın.
  3. Bir hata sonucu belirleyin.

Seçenek (1), isteğin filtrenin anladığı hiçbir kimlik bilgisi olmadığı anlamına gelir. Seçenek (2), filtrenin isteğin kimliğini başarıyla doğrulamış olduğu anlamına gelir. Seçenek (3), isteğin geçersiz kimlik bilgilerine (yanlış parola gibi) sahip olduğu ve bu da bir hata yanıtı tetiklediği anlamına gelir.

Burada AuthenticateAsync'i uygulamaya yönelik genel bir ana hat yer alır.

  1. İstekte kimlik bilgilerini arayın.
  2. Kimlik bilgileri yoksa hiçbir şey yapma ve döndür (no-op).
  3. Kimlik bilgileri varsa ancak filtre kimlik doğrulama düzenini tanımıyorsa, hiçbir şey yapma ve döndür (no-op). İşlem hattındaki başka bir filtre şemayı anlayabilir.
  4. Filtrenin anladığı kimlik bilgileri varsa kimliklerini doğrulamayı deneyin.
  5. Kimlik bilgileri hatalıysa ayarını context.ErrorResultyaparak 401 değerini döndürin.
  6. Kimlik bilgileri geçerliyse, bir IPrincipal oluşturun ve ayarlayın context.Principal.

Aşağıdaki kod, Temel Kimlik Doğrulaması örneğindeki AuthenticateAsync yöntemini gösterir. Açıklamalar her adımı gösterir. Kod çeşitli hata türlerini gösterir: Kimlik bilgisi olmayan yetkilendirme üst bilgisi, hatalı biçimlendirilmiş kimlik bilgileri ve hatalı kullanıcı adı/parola.

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;
    }

}

Hata Sonucunu Belirleme

Kimlik bilgileri geçersizse, filtrenin hata yanıtı oluşturan bir context.ErrorResult olarak ayarlanması gerekir. IHttpActionResult hakkında daha fazla bilgi için bkz. Web API 2'de Eylem Sonuçları.

Temel Kimlik Doğrulaması örneği, bu amaca uygun bir AuthenticationFailureResult sınıf içerir.

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'ı Uygulama

ChallengeAsync yönteminin amacı, gerekirse yanıta kimlik doğrulama sınamaları eklemektir. Yöntem imzası aşağıdadır:

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

yöntemi, istek işlem hattındaki tüm kimlik doğrulama filtrelerinde çağrılır.

ChallengeAsync'in HTTP yanıtı oluşturulmadan önce ve denetleyici eylemi çalışmadan önce bile çağrıldığını anlamak önemlidir. ChallengeAsync çağrıldığında, context.Result daha sonra HTTP yanıtını oluşturmak için kullanılan bir IHttpActionResult içerir. Dolayısıyla ChallengeAsync çağrıldığında HTTP yanıtı hakkında henüz bir şey bilmiyorsunuz. ChallengeAsync yöntemi özgün değerini context.Result yeni bir IHttpActionResult ile değiştirmelidir. Bu IHttpActionResult özgün context.Resultöğesini sarmalamalıdır.

ChallengeAsync diyagramı

özgün IHttpActionResult öğesini iç sonucu ve yeni IHttpActionResult'udış sonucu çağıracağım. Dışsal sonuç, aşağıdakileri gerçekleştirmelidir:

  1. HTTP yanıtını oluşturmak için iç sonucu çağırın.
  2. Yanıtı inceleyin.
  3. Gerekirse yanıta bir kimlik doğrulama sınaması ekleyin.

Aşağıdaki örnek, Temel Kimlik Doğrulaması örneğinden alınmıştır. Dış sonuç için bir IHttpActionResult tanımlar.

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;
    }
}

özelliği, InnerResult iç IHttpActionResult değerini barındırıyor. Challenge özelliği bir Www-Authentication üst bilgisini temsil eder. ExecuteAsync'ın önce HTTP yanıtını oluşturmak için InnerResult.ExecuteAsync'i çağırdığını ve ardından gerekirse yönlendirmeyi eklediğine dikkat edin.

Sınamayı eklemeden önce yanıt kodunu denetleyin. Çoğu kimlik doğrulama düzeni yalnızca burada gösterildiği gibi yanıt 401 olduğunda bir sınama ekler. Ancak, bazı kimlik doğrulama düzenleri başarı yanıtına bir sınama ekler. Örneğin, bkz. Müzakere (RFC 4559).

Verilen AddChallengeOnUnauthorizedResult sınıfı dikkate alındığında ChallengeAsync içindeki gerçek kod basittir. Sonucu oluşturup sadece context.Result öğesine ekleyin.

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

Not: Temel Kimlik Doğrulama örneği, bu mantığı bir uzantı yöntemine yerleştirerek biraz soyutlar.

Kimlik Doğrulama Filtrelerini Host-Level Kimlik Doğrulaması ile Birleştirme

"Konak düzeyinde kimlik doğrulaması", istek Web API çerçevesine ulaşmadan önce konak (IIS gibi) tarafından gerçekleştirilen kimlik doğrulamasıdır.

Genellikle, uygulamanızın geri kalanı için konak düzeyinde kimlik doğrulamasını etkinleştirmek, ancak Web API denetleyicileriniz için devre dışı bırakmak isteyebilirsiniz. Örneğin, tipik bir senaryo, sunucu düzeyinde Forms authentication'ı etkinleştirmek, ancak Web API için belirteç tabanlı kimlik doğrulama kullanmaktır.

Web API işlem hattı içinde konak düzeyinde kimlik doğrulamayı devre dışı bırakmak için yapılandırmada config.SuppressHostPrincipal() işlevini çağırın. Bu, Web API'sinin Web API işlem hattına giren tüm isteklerden IPrincipal'ı kaldırmasına neden olur. Etkili bir şekilde isteğin "kimlik doğrulamasını kaldırıyor".

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

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

Ek Kaynaklar

ASP.NET Web API Güvenlik Filtreleri (MSDN Magazine)