Bagikan melalui


Filter Autentikasi di ASP.NET Web API 2

oleh Mike Wasson

Filter autentikasi adalah komponen yang mengautentikasi permintaan HTTP. Web API 2 dan MVC 5 keduanya mendukung filter autentikasi, tetapi sedikit berbeda, sebagian besar dalam konvensi penamaan untuk antarmuka filter. Topik ini menjelaskan filter autentikasi API Web.

Filter autentikasi memungkinkan Anda mengatur skema autentikasi untuk pengontrol atau tindakan individual. Dengan demikian, aplikasi Anda dapat mendukung mekanisme autentikasi yang berbeda untuk sumber daya HTTP yang berbeda.

Dalam artikel ini, saya akan menampilkan kode dari sampel Autentikasi Dasar di https://github.com/aspnet/samples. Sampel menunjukkan filter autentikasi yang mengimplementasikan skema Autentikasi Akses Dasar HTTP (RFC 2617). Filter diimplementasikan dalam kelas bernama IdentityBasicAuthenticationAttribute. Saya tidak akan menampilkan semua kode dari sampel, hanya bagian yang menggambarkan cara menulis filter autentikasi.

Mengatur Filter Autentikasi

Seperti filter lain, filter autentikasi dapat diterapkan per pengontrol, per tindakan, atau secara global ke semua pengontrol API Web.

Untuk menerapkan filter autentikasi ke pengontrol, hiasi kelas pengontrol dengan atribut filter. Kode berikut mengatur [IdentityBasicAuthentication] filter pada kelas pengontrol, yang memungkinkan Autentikasi Dasar untuk semua tindakan pengontrol.

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

Untuk menerapkan filter ke satu tindakan, hiasi tindakan dengan filter. Kode berikut mengatur [IdentityBasicAuthentication] filter pada metode pengontrol Post .

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

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

Untuk menerapkan filter ke semua pengontrol API Web, tambahkan ke GlobalConfiguration.Filters.

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

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

Menerapkan Filter Autentikasi API Web

Di API Web, filter autentikasi mengimplementasikan antarmuka System.Web.Http.Filters.IAuthenticationFilter . Mereka juga harus mewarisi dari System.Attribute, untuk diterapkan sebagai atribut.

Antarmuka IAuthenticationFilter memiliki dua metode:

  • AuthenticateAsync mengautentikasi permintaan dengan memvalidasi kredensial dalam permintaan, jika ada.
  • ChallengeAsync menambahkan tantangan autentikasi ke respons HTTP, jika diperlukan.

Metode ini sesuai dengan alur autentikasi yang ditentukan dalam RFC 2612 dan RFC 2617:

  1. Klien mengirim kredensial di header Otorisasi. Ini biasanya terjadi setelah klien menerima respons 401 (Tidak sah) dari server. Namun, klien dapat mengirim kredensial dengan permintaan apa pun, bukan hanya setelah mendapatkan 401.
  2. Jika server tidak menerima kredensial, server mengembalikan respons 401 (Tidak Sah). Respons mencakup header Www-Authenticate yang berisi satu atau beberapa tantangan. Setiap tantangan menentukan skema autentikasi yang dikenali oleh server.

Server juga dapat mengembalikan 401 dari permintaan anonim. Bahkan, itu biasanya bagaimana proses autentikasi dimulai:

  1. Klien mengirimkan permintaan anonim.
  2. Server mengembalikan 401.
  3. Klien mengirim ulang permintaan dengan kredensial.

Alur ini mencakup langkah-langkah autentikasi dan otorisasi .

  • Autentikasi membuktikan identitas klien.
  • Otorisasi menentukan apakah klien dapat mengakses sumber daya tertentu.

Di API Web, filter autentikasi menangani autentikasi, tetapi bukan otorisasi. Otorisasi harus dilakukan oleh filter otorisasi atau di dalam tindakan pengontrol.

Berikut adalah alur dalam alur Web API 2:

  1. Sebelum memanggil tindakan, Web API membuat daftar filter autentikasi untuk tindakan tersebut. Ini termasuk filter dengan cakupan tindakan, cakupan pengontrol, dan cakupan global.
  2. API Web memanggil AuthenticateAsync pada setiap filter dalam daftar. Setiap filter dapat memvalidasi kredensial dalam permintaan. Jika ada filter yang berhasil memvalidasi kredensial, filter akan membuat IPrincipal dan melampirkannya ke permintaan. Filter juga dapat memicu kesalahan pada saat ini. Jika demikian, sisa alur tidak berjalan.
  3. Dengan asumsi tidak ada kesalahan, permintaan mengalir melalui sisa alur.
  4. Terakhir, Web API memanggil setiap metode ChallengeAsync filter autentikasi. Filter menggunakan metode ini untuk menambahkan tantangan ke respons, jika diperlukan. Biasanya (tetapi tidak selalu) yang akan terjadi sebagai respons terhadap kesalahan 401.

Diagram berikut menunjukkan dua kemungkinan kasus. Pada yang pertama, filter autentikasi berhasil mengautentikasi permintaan, filter otorisasi mengotorisasi permintaan, dan tindakan pengontrol mengembalikan 200 (OK).

Diagram autentikasi yang berhasil

Dalam contoh kedua, filter autentikasi mengautentikasi permintaan, tetapi filter otorisasi mengembalikan 401 (Tidak Sah). Dalam hal ini, tindakan pengontrol tidak dipanggil. Filter autentikasi menambahkan header Www-Authenticate ke respons.

Diagram autentikasi yang tidak sah

Kombinasi lain dimungkinkan—misalnya, jika tindakan pengontrol mengizinkan permintaan anonim, Anda mungkin memiliki filter autentikasi tetapi tidak ada otorisasi.

Menerapkan Metode AuthenticateAsync

Metode AuthenticateAsync mencoba mengautentikasi permintaan. Berikut adalah tanda tangan metode:

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

Metode AuthenticateAsync harus melakukan salah satu hal berikut ini:

  1. Tidak ada (no-op).
  2. Buat IPrincipal dan atur sesuai permintaan.
  3. Atur hasil kesalahan.

Opsi (1) berarti permintaan tidak memiliki kredensial apa pun yang dipahami filter. Opsi (2) berarti filter berhasil mengautentikasi permintaan. Opsi (3) berarti permintaan memiliki kredensial yang tidak valid (seperti kata sandi yang salah), yang memicu respons kesalahan.

Berikut adalah kerangka umum untuk menerapkan AuthenticateAsync.

  1. Cari kredensial dalam permintaan.
  2. Jika tidak ada kredensial, jangan lakukan apa pun dan kembalikan (tanpa operasi).
  3. Jika ada kredensial tetapi filter tidak mengenali skema autentikasi, jangan lakukan apa pun dan kembalikan (tanpa operasi). Filter lain dalam alur mungkin memahami skema.
  4. Jika ada kredensial yang dipahami filter, coba autentikasi.
  5. Jika kredensial buruk, kembalikan 401 dengan mengatur context.ErrorResult.
  6. Jika kredensial valid, buat IPrincipal dan atur context.Principal.

Kode berikut menunjukkan metode AuthenticateAsync dari sampel Autentikasi Dasar . Komentar menunjukkan setiap langkah. Kode menunjukkan beberapa jenis kesalahan: Header otorisasi tanpa kredensial, kredensial cacat, dan nama pengguna/kata sandi yang buruk.

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

}

Mengatur Hasil Kesalahan

Jika kredensial tidak valid, filter harus diatur context.ErrorResult ke IHttpActionResult yang membuat respons kesalahan. Untuk informasi selengkapnya tentang IHttpActionResult, lihat Hasil Tindakan di Web API 2.

Sampel Autentikasi Dasar mencakup AuthenticationFailureResult kelas yang cocok untuk tujuan ini.

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

Menerapkan ChallengeAsync

Tujuan dari metode ChallengeAsync adalah untuk menambahkan tantangan autentikasi ke respons, jika diperlukan. Berikut adalah tanda tangan metode:

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

Metode ini dipanggil pada setiap filter autentikasi dalam alur permintaan.

Penting untuk dipahami bahwa ChallengeAsync dipanggil sebelum respons HTTP dibuat, dan mungkin bahkan sebelum tindakan pengontrol berjalan. Saat ChallengeAsync dipanggil, context.Result berisi IHttpActionResult, yang digunakan nanti untuk membuat respons HTTP. Jadi ketika ChallengeAsync dipanggil, Anda belum tahu apa-apa tentang respons HTTP. Metode ChallengeAsync harus mengganti nilai context.Result asli dengan IHttpActionResult baru. IHttpActionResult ini harus membungkus yang aslicontext.Result.

Diagram of ChallengeAsync

Saya akan memanggil IHttpActionResult asli hasil dalam, dan IHttpActionResult baru hasil luar. Hasil luar harus melakukan hal berikut:

  1. Panggil hasil dalam untuk membuat respons HTTP.
  2. Periksa responsnya.
  3. Tambahkan tantangan autentikasi ke respons, jika diperlukan.

Contoh berikut diambil dari sampel Autentikasi Dasar. Ini mendefinisikan IHttpActionResult untuk hasil luar.

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

Properti InnerResult menyimpan IHttpActionResult dalam. Properti Challenge mewakili header Www-Authentication. Perhatikan bahwa ExecuteAsync pertama kali memanggil InnerResult.ExecuteAsync untuk membuat respons HTTP, lalu menambahkan tantangan jika diperlukan.

Periksa kode respons sebelum menambahkan tantangan. Sebagian besar skema autentikasi hanya menambahkan tantangan jika responsnya adalah 401, seperti yang ditunjukkan di sini. Namun, beberapa skema autentikasi menambahkan tantangan pada respons keberhasilan. Misalnya, lihat Negosiasi (RFC 4559).

AddChallengeOnUnauthorizedResult Mengingat kelas , kode aktual dalam ChallengeAsync sederhana. Anda cukup membuat hasilnya dan melampirkannya ke 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);
}

Catatan: Sampel Autentikasi Dasar sedikit mengabstraksi logika ini, dengan menempatkannya dalam metode ekstensi.

Menggabungkan Filter Autentikasi dengan Autentikasi Host-Level

"Autentikasi tingkat host" adalah autentikasi yang dilakukan oleh host (seperti IIS), sebelum permintaan mencapai kerangka kerja API Web.

Seringkali, Anda mungkin ingin mengaktifkan autentikasi tingkat host untuk sisa aplikasi Anda, tetapi menonaktifkannya untuk pengontrol API Web Anda. Misalnya, skenario umumnya adalah mengaktifkan Autentikasi Formulir di tingkat host, tetapi menggunakan autentikasi berbasis token untuk WEB API.

Untuk menonaktifkan autentikasi tingkat host di dalam alur API Web, panggil config.SuppressHostPrincipal() konfigurasi Anda. Hal ini menyebabkan Web API menghapus IPrincipal dari permintaan apa pun yang memasuki alur API Web. Secara efektif, ini "membatalkan autentikasi" permintaan.

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

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

Sumber Daya Tambahan

ASP.NET Web API Security Filters (MSDN Magazine)