Autentikasi di WinHTTP

Beberapa server dan proksi HTTP memerlukan autentikasi sebelum mengizinkan akses ke sumber daya di Internet. Fungsi Microsoft Windows HTTP Services (WinHTTP) mendukung server dan autentikasi proksi untuk sesi HTTP.

Tentang Autentikasi HTTP

Jika autentikasi diperlukan, aplikasi HTTP menerima kode status 401 (server memerlukan autentikasi) atau 407 (proksi memerlukan autentikasi). Bersama dengan kode status, proksi atau server mengirim satu atau beberapa header autentikasi: WWW-Authenticate (untuk autentikasi server) atau Proxy-Authenticate (untuk autentikasi proksi).

Setiap header autentikasi berisi skema autentikasi yang didukung dan, untuk skema Dasar dan Hash, realm. Jika beberapa skema autentikasi didukung, server mengembalikan beberapa header autentikasi. Nilai realm peka huruf besar/kecil dan mendefinisikan sekumpulan server atau proksi yang kredensialnya sama diterima. Misalnya, header "WWW-Authenticate: Basic Realm="example"" mungkin dikembalikan saat autentikasi server diperlukan. Header ini menentukan bahwa kredensial pengguna harus disediakan untuk domain "contoh".

Aplikasi HTTP dapat menyertakan bidang header otorisasi dengan permintaan yang dikirimnya ke server. Header otorisasi berisi skema autentikasi dan respons yang sesuai yang diperlukan oleh skema tersebut. Misalnya, header "Otorisasi: Nama pengguna:kata sandi> dasar<" akan ditambahkan ke permintaan dan dikirim ke server jika klien menerima header respons "WWW-Authenticate: Basic Realm="example"".

Catatan

Meskipun ditampilkan di sini sebagai teks biasa, nama pengguna dan kata sandi sebenarnya dikodekan base64.

 

Ada dua jenis umum skema autentikasi:

  • Skema autentikasi dasar, di mana nama pengguna dan kata sandi dikirim dalam teks yang jelas ke server.

    Skema autentikasi Dasar didasarkan pada model yang harus diidentifikasi klien dengan nama pengguna dan kata sandi untuk setiap realm. Server melayani permintaan hanya jika permintaan dikirim dengan header otorisasi yang menyertakan nama pengguna dan kata sandi yang valid.

  • Skema respons tantangan, seperti Kerberos, di mana server menantang klien dengan data autentikasi. Klien mengubah data dengan kredensial pengguna dan mengirim data yang diubah kembali ke server untuk autentikasi.

    Skema respons tantangan memungkinkan autentikasi yang lebih aman. Dalam skema respons tantangan, nama pengguna dan kata sandi tidak pernah dikirimkan melalui jaringan. Setelah klien memilih skema respons tantangan, server mengembalikan kode status yang sesuai dengan tantangan yang berisi data autentikasi untuk skema tersebut. Klien kemudian mengirim ulang permintaan dengan respons yang tepat untuk mendapatkan layanan yang diminta. Skema respons tantangan dapat mengambil beberapa pertukaran untuk diselesaikan.

Tabel berikut ini berisi skema autentikasi yang didukung oleh WinHTTP, jenis autentikasi, dan deskripsi skema.

Skema Jenis Deskripsi
Dasar (teks biasa) Dasar Menggunakan string yang dikodekan base64 yang berisi nama pengguna dan kata sandi.
digest Respons tantangan Tantangan menggunakan nilai nonce (string data yang ditentukan server). Respons yang valid berisi checksum nama pengguna, kata sandi, nilai nonce yang diberikan, kata kerja HTTP, dan Pengidentifikasi Sumber Daya Seragam (URI) yang diminta.
NTLM Respons tantangan Mengharuskan data autentikasi diubah dengan kredensial pengguna untuk membuktikan identitas. Agar autentikasi NTLM berfungsi dengan benar, beberapa pertukaran harus berlangsung pada koneksi yang sama. Oleh karena itu, autentikasi NTLM tidak dapat digunakan jika proksi intervensi tidak mendukung koneksi tetap hidup. Autentikasi NTLM juga gagal jika WinHttpSetOption digunakan dengan bendera WINHTTP_DISABLE_KEEP_ALIVE yang menonaktifkan semantik tetap hidup.
Paspor Respons tantangan Menggunakan Microsoft Passport 1.4.
Negosiasi Respons tantangan Jika server dan klien menggunakan Windows 2000 atau yang lebih baru, autentikasi Kerberos digunakan. Jika tidak, autentikasi NTLM digunakan. Kerberos tersedia di Windows 2000 dan sistem operasi yang lebih baru dan dianggap lebih aman daripada autentikasi NTLM. Agar autentikasi Negosiasi berfungsi dengan benar, beberapa pertukaran harus berlangsung pada koneksi yang sama. Oleh karena itu, Autentikasi negosiasi tidak dapat digunakan jika proksi intervensi tidak mendukung koneksi tetap hidup. Negosiasi autentikasi juga gagal jika WinHttpSetOption digunakan dengan bendera WINHTTP_DISABLE_KEEP_ALIVE yang menonaktifkan semantik tetap hidup. Skema autentikasi Negosiasi terkadang disebut autentikasi Windows Terintegrasi.

 

Autentikasi di Aplikasi WinHTTP

Antarmuka pemrograman aplikasi WinHTTP (API) menyediakan dua fungsi yang digunakan untuk mengakses sumber daya Internet dalam situasi di mana autentikasi diperlukan: WinHttpSetCredentials dan WinHttpQueryAuthSchemes.

Saat respons diterima dengan kode status 401 atau 407, WinHttpQueryAuthSchemes dapat digunakan untuk mengurai header autentikasi untuk menentukan skema autentikasi yang didukung dan target autentikasi. Target autentikasi adalah server atau proksi yang meminta autentikasi. WinHttpQueryAuthSchemes juga menentukan skema autentikasi pertama, dari skema yang tersedia, berdasarkan preferensi skema autentikasi yang disarankan oleh server. Metode untuk memilih skema autentikasi ini adalah perilaku yang disarankan oleh RFC 2616.

WinHttpSetCredentials memungkinkan aplikasi menentukan skema autentikasi yang digunakan bersama dengan nama pengguna dan kata sandi yang valid untuk digunakan di server target atau proksi. Setelah mengatur kredensial dan mengirim ulang permintaan, header yang diperlukan dihasilkan dan ditambahkan ke permintaan secara otomatis. Karena beberapa skema autentikasi memerlukan beberapa transaksi WinHttpSendRequest bisa mengembalikan kesalahan, ERROR_WINHTTP_RESEND_REQUEST. Ketika kesalahan ini ditemui, aplikasi harus terus mengirim ulang permintaan hingga respons diterima yang tidak berisi kode status 401 atau 407. Kode status 200 menunjukkan bahwa sumber daya tersedia dan permintaan berhasil. Lihat Kode Status HTTP untuk kode status tambahan yang dapat dikembalikan.

Jika skema autentikasi dan kredensial yang dapat diterima diketahui sebelum permintaan dikirim ke server, aplikasi bisa memanggil WinHttpSetCredentials sebelum memanggil WinHttpSendRequest. Dalam hal ini, WinHTTP mencoba pra-autentikasi dengan server dengan memberikan kredensial atau data autentikasi dalam permintaan awal ke server. Pra-autentikasi dapat mengurangi jumlah pertukaran dalam proses autentikasi dan oleh karena itu meningkatkan performa aplikasi.

Praautentikasi dapat digunakan dengan skema autentikasi berikut:

  • Dasar - selalu mungkin.
  • Negosiasi penyelesaian ke Kerberos - sangat mungkin; satu-satunya pengecualian adalah ketika penyimpangan waktu mati antara klien dan pengontrol domain.
  • (Negosiasi penyelesaian ke NTLM) - tidak pernah mungkin.
  • NTLM - mungkin hanya di Windows Server 2008 R2.
  • Digest - tidak pernah mungkin.
  • Paspor - tidak pernah mungkin; setelah respons tantangan awal, WinHTTP menggunakan cookie untuk melakukan pra-autentikasi ke Paspor.

Aplikasi WinHTTP yang khas menyelesaikan langkah-langkah berikut untuk menangani autentikasi.

Kredensial yang ditetapkan oleh WinHttpSetCredentials hanya digunakan untuk satu permintaan. WinHTTP tidak menyimpan kredensial untuk digunakan dalam permintaan lain, yang berarti bahwa aplikasi harus ditulis yang dapat menanggapi beberapa permintaan. Jika koneksi terautentikasi digunakan kembali, permintaan lain mungkin tidak ditantang, tetapi kode Anda harus dapat menanggapi permintaan kapan saja.

Contoh: Mengambil Dokumen

Kode sampel berikut mencoba mengambil dokumen tertentu dari server HTTP. Kode status diambil dari respons untuk menentukan apakah autentikasi diperlukan. Jika kode status 200 ditemukan, dokumen tersedia. Jika kode status 401 atau 407 ditemukan, autentikasi diperlukan sebelum dokumen dapat diambil. Untuk kode status lainnya, pesan kesalahan ditampilkan. Lihat Kode Status HTTP untuk daftar kemungkinan kode status.

#include <windows.h>
#include <winhttp.h>
#include <stdio.h>

#pragma comment(lib, "winhttp.lib")

DWORD ChooseAuthScheme( DWORD dwSupportedSchemes )
{
  //  It is the server's responsibility only to accept 
  //  authentication schemes that provide a sufficient
  //  level of security to protect the servers resources.
  //
  //  The client is also obligated only to use an authentication
  //  scheme that adequately protects its username and password.
  //
  //  Thus, this sample code does not use Basic authentication  
  //  becaus Basic authentication exposes the client's username
  //  and password to anyone monitoring the connection.
  
  if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NEGOTIATE )
    return WINHTTP_AUTH_SCHEME_NEGOTIATE;
  else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NTLM )
    return WINHTTP_AUTH_SCHEME_NTLM;
  else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_PASSPORT )
    return WINHTTP_AUTH_SCHEME_PASSPORT;
  else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_DIGEST )
    return WINHTTP_AUTH_SCHEME_DIGEST;
  else
    return 0;
}

struct SWinHttpSampleGet
{
  LPCWSTR szServer;
  LPCWSTR szPath;
  BOOL fUseSSL;
  LPCWSTR szServerUsername;
  LPCWSTR szServerPassword;
  LPCWSTR szProxyUsername;
  LPCWSTR szProxyPassword;
};

void WinHttpAuthSample( IN SWinHttpSampleGet *pGetRequest )
{
  DWORD dwStatusCode = 0;
  DWORD dwSupportedSchemes;
  DWORD dwFirstScheme;
  DWORD dwSelectedScheme;
  DWORD dwTarget;
  DWORD dwLastStatus = 0;
  DWORD dwSize = sizeof(DWORD);
  BOOL  bResults = FALSE;
  BOOL  bDone = FALSE;

  DWORD dwProxyAuthScheme = 0;
  HINTERNET  hSession = NULL, 
             hConnect = NULL,
             hRequest = NULL;

  // Use WinHttpOpen to obtain a session handle.
  hSession = WinHttpOpen( L"WinHTTP Example/1.0",  
                          WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                          WINHTTP_NO_PROXY_NAME, 
                          WINHTTP_NO_PROXY_BYPASS, 0 );

  INTERNET_PORT nPort = ( pGetRequest->fUseSSL ) ? 
                        INTERNET_DEFAULT_HTTPS_PORT  :
                        INTERNET_DEFAULT_HTTP_PORT;

  // Specify an HTTP server.
  if( hSession )
    hConnect = WinHttpConnect( hSession, 
                               pGetRequest->szServer, 
                               nPort, 0 );

  // Create an HTTP request handle.
  if( hConnect )
    hRequest = WinHttpOpenRequest( hConnect, 
                                   L"GET", 
                                   pGetRequest->szPath,
                                   NULL, 
                                   WINHTTP_NO_REFERER, 
                                   WINHTTP_DEFAULT_ACCEPT_TYPES,
                                   ( pGetRequest->fUseSSL ) ? 
                                       WINHTTP_FLAG_SECURE : 0 );

  // Continue to send a request until status code 
  // is not 401 or 407.
  if( hRequest == NULL )
    bDone = TRUE;

  while( !bDone )
  {
    //  If a proxy authentication challenge was responded to, reset
    //  those credentials before each SendRequest, because the proxy  
    //  may require re-authentication after responding to a 401 or  
    //  to a redirect. If you don't, you can get into a 
    //  407-401-407-401- loop.
    if( dwProxyAuthScheme != 0 )
      bResults = WinHttpSetCredentials( hRequest, 
                                        WINHTTP_AUTH_TARGET_PROXY, 
                                        dwProxyAuthScheme, 
                                        pGetRequest->szProxyUsername,
                                        pGetRequest->szProxyPassword,
                                        NULL );
    // Send a request.
    bResults = WinHttpSendRequest( hRequest,
                                   WINHTTP_NO_ADDITIONAL_HEADERS,
                                   0,
                                   WINHTTP_NO_REQUEST_DATA,
                                   0, 
                                   0, 
                                   0 );

    // End the request.
    if( bResults )
      bResults = WinHttpReceiveResponse( hRequest, NULL );

    // Resend the request in case of 
    // ERROR_WINHTTP_RESEND_REQUEST error.
    if( !bResults && GetLastError( ) == ERROR_WINHTTP_RESEND_REQUEST)
        continue;

    // Check the status code.
    if( bResults ) 
      bResults = WinHttpQueryHeaders( hRequest, 
                                      WINHTTP_QUERY_STATUS_CODE |
                                      WINHTTP_QUERY_FLAG_NUMBER,
                                      NULL, 
                                      &dwStatusCode, 
                                      &dwSize, 
                                      NULL );

    if( bResults )
    {
      switch( dwStatusCode )
      {
        case 200: 
          // The resource was successfully retrieved.
          // You can use WinHttpReadData to read the 
          // contents of the server's response.
          printf( "The resource was successfully retrieved.\n" );
          bDone = TRUE;
          break;

        case 401:
          // The server requires authentication.
          printf(" The server requires authentication. Sending credentials...\n" );

          // Obtain the supported and preferred schemes.
          bResults = WinHttpQueryAuthSchemes( hRequest, 
                                              &dwSupportedSchemes, 
                                              &dwFirstScheme, 
                                              &dwTarget );

          // Set the credentials before resending the request.
          if( bResults )
          {
            dwSelectedScheme = ChooseAuthScheme( dwSupportedSchemes);

            if( dwSelectedScheme == 0 )
              bDone = TRUE;
            else
              bResults = WinHttpSetCredentials( hRequest, 
                                        dwTarget, 
                                        dwSelectedScheme,
                                        pGetRequest->szServerUsername,
                                        pGetRequest->szServerPassword,
                                        NULL );
          }

          // If the same credentials are requested twice, abort the
          // request.  For simplicity, this sample does not check
          // for a repeated sequence of status codes.
          if( dwLastStatus == 401 )
            bDone = TRUE;

          break;

        case 407:
          // The proxy requires authentication.
          printf( "The proxy requires authentication.  Sending credentials...\n" );

          // Obtain the supported and preferred schemes.
          bResults = WinHttpQueryAuthSchemes( hRequest, 
                                              &dwSupportedSchemes, 
                                              &dwFirstScheme, 
                                              &dwTarget );

          // Set the credentials before resending the request.
          if( bResults )
            dwProxyAuthScheme = ChooseAuthScheme(dwSupportedSchemes);

          // If the same credentials are requested twice, abort the
          // request.  For simplicity, this sample does not check 
          // for a repeated sequence of status codes.
          if( dwLastStatus == 407 )
            bDone = TRUE;
          break;

        default:
          // The status code does not indicate success.
          printf("Error. Status code %d returned.\n", dwStatusCode);
          bDone = TRUE;
      }
    }

    // Keep track of the last status code.
    dwLastStatus = dwStatusCode;

    // If there are any errors, break out of the loop.
    if( !bResults ) 
        bDone = TRUE;
  }

  // Report any errors.
  if( !bResults )
  {
    DWORD dwLastError = GetLastError( );
    printf( "Error %d has occurred.\n", dwLastError );
  }

  // Close any open handles.
  if( hRequest ) WinHttpCloseHandle( hRequest );
  if( hConnect ) WinHttpCloseHandle( hConnect );
  if( hSession ) WinHttpCloseHandle( hSession );
}

Kebijakan Masuk Otomatis

Kebijakan masuk otomatis (masuk otomatis) menentukan kapan WinHTTP dapat diterima untuk menyertakan kredensial default dalam permintaan. Kredensial default adalah token utas saat ini atau token sesi tergantung pada apakah WinHTTP digunakan dalam mode sinkron atau asinkron. Token utas digunakan dalam mode sinkron, dan token sesi digunakan dalam mode asinkron. Kredensial default ini sering kali merupakan nama pengguna dan kata sandi yang digunakan untuk masuk ke Microsoft Windows.

Kebijakan masuk otomatis diterapkan untuk mencegah kredensial ini digunakan dengan santai untuk mengautentikasi terhadap server yang tidak tepercaya. Secara default, tingkat keamanan diatur ke WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, yang memungkinkan kredensial default digunakan hanya untuk permintaan Intranet. Kebijakan masuk otomatis hanya berlaku untuk skema autentikasi NTLM dan Negosiasi. Kredensial tidak pernah secara otomatis ditransmisikan dengan skema lain.

Kebijakan masuk otomatis dapat diatur menggunakan fungsi WinHttpSetOption dengan bendera WINHTTP_OPTION_AUTOLOGON_POLICY . Bendera ini hanya berlaku untuk handel permintaan. Ketika kebijakan diatur ke WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW, kredensial default dapat dikirim ke semua server. Ketika kebijakan diatur ke WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH, kredensial default tidak dapat digunakan untuk autentikasi. Sangat disarankan agar Anda menggunakan masuk otomatis di tingkat MEDIUM.

Nama Pengguna dan Kata Sandi Tersimpan

Windows XP memperkenalkan konsep Nama Pengguna dan Kata Sandi Tersimpan. Jika kredensial Paspor pengguna disimpan melalui Wizard Pendaftaran Paspor atau Dialog Kredensial standar, kredensial tersebut disimpan di Nama Pengguna dan Kata Sandi tersimpan. Saat menggunakan WinHTTP di Windows XP atau yang lebih baru, WinHTTP secara otomatis menggunakan kredensial di Nama Pengguna dan Kata Sandi tersimpan jika kredensial tidak diatur secara eksplisit. Ini mirip dengan dukungan kredensial masuk default untuk NTLM/Kerberos. Namun, penggunaan kredensial Paspor default tidak tunduk pada pengaturan kebijakan masuk otomatis.