Udostępnij za pośrednictwem


Uwierzytelnianie w usłudze WinHTTP

Niektóre serwery HTTP i serwery proxy wymagają uwierzytelniania przed zezwoleniem na dostęp do zasobów w Internecie. Funkcje usług HTTP systemu Microsoft Windows (WinHTTP) obsługują uwierzytelnianie serwera i serwera proxy dla sesji HTTP.

Informacje o uwierzytelnianiu HTTP

Jeśli wymagane jest uwierzytelnianie, aplikacja HTTP otrzymuje kod stanu 401 (serwer wymaga uwierzytelniania) lub 407 (serwer proxy wymaga uwierzytelniania). Wraz z kodem stanu serwer proxy lub serwer wysyła co najmniej jeden nagłówek uwierzytelniania: WWW-Authenticate (na potrzeby uwierzytelniania serwera) lub Proxy-Authenticate (na potrzeby uwierzytelniania serwera proxy).

Każdy nagłówek uwierzytelniania zawiera obsługiwany schemat uwierzytelniania, a w przypadku schematów Podstawowe i Szyfrowane obszar. Jeśli jest obsługiwanych wiele schematów uwierzytelniania, serwer zwraca wiele nagłówków uwierzytelniania. Wartość obszaru uwzględnia wielkość liter i definiuje zestaw serwerów lub serwerów proxy, dla których akceptowane są te same poświadczenia. Na przykład nagłówek "WWW-Authentication: Basic Realm="example"" może zostać zwrócony, gdy wymagane jest uwierzytelnianie serwera. Ten nagłówek określa, że poświadczenia użytkownika muszą być podane dla domeny "przykładowej".

Aplikacja HTTP może zawierać pole nagłówka autoryzacji z żądaniem wysyłanym do serwera. Nagłówek autoryzacji zawiera schemat uwierzytelniania i odpowiednią odpowiedź wymaganą przez ten schemat. Na przykład nagłówek "Authorization: Basic <username:password>" zostanie dodany do żądania i wysłany do serwera, jeśli klient odebrał nagłówek odpowiedzi "WWW-Authentication: Basic Realm="example".

Nuta

Chociaż są one wyświetlane tutaj jako zwykły tekst, nazwa użytkownika i hasło są rzeczywiście kodowane base64.

 

Istnieją dwa ogólne typy schematów uwierzytelniania:

  • Podstawowy schemat uwierzytelniania, w którym nazwa użytkownika i hasło są wysyłane w postaci zwykłego tekstu do serwera.

    Schemat uwierzytelniania podstawowego jest oparty na modelu, który klient musi identyfikować się przy użyciu nazwy użytkownika i hasła dla każdego obszaru. Serwer obsługuje żądanie tylko wtedy, gdy żądanie jest wysyłane z nagłówkiem autoryzacji, który zawiera prawidłową nazwę użytkownika i hasło.

  • Schematy odpowiedzi na wyzwania, takie jak Kerberos, w których serwer kwestionuje klienta przy użyciu danych uwierzytelniania. Klient przekształca dane przy użyciu poświadczeń użytkownika i wysyła przekształcone dane z powrotem do serwera na potrzeby uwierzytelniania.

    Schematy odpowiedzi na wyzwania umożliwiają bezpieczniejsze uwierzytelnianie. W schemacie odpowiedzi na żądanie nazwa użytkownika i hasło nigdy nie są przesyłane za pośrednictwem sieci. Po wybraniu przez klienta schematu odpowiedzi na żądanie serwer zwraca odpowiedni kod stanu z wyzwaniem zawierającym dane uwierzytelniania dla tego schematu. Następnie klient ponownie wysyła żądanie z odpowiednią odpowiedzią, aby uzyskać żądaną usługę. Schematy odpowiedzi na wyzwania mogą wykonać wiele wymian.

Poniższa tabela zawiera schematy uwierzytelniania obsługiwane przez winHTTP, typ uwierzytelniania i opis schematu.

Plan Typ Opis
Podstawowa (zwykły tekst) Podstawowy Używa zakodowanego w formacie base64 ciągu zawierającego nazwę użytkownika i hasło.
Trawić Odpowiedź na żądanie Wyzwania związane z użyciem wartości innej niż (ciąg danych określony przez serwer). Prawidłowa odpowiedź zawiera sumę kontrolną nazwy użytkownika, hasło, daną wartość inną niż, czasownik http i żądany identyfikator URI (Uniform Resource Identifier).
NTLM Odpowiedź na żądanie Wymaga, aby dane uwierzytelniania zostały przekształcone przy użyciu poświadczeń użytkownika w celu potwierdzenia tożsamości. Aby uwierzytelnianie NTLM działało poprawnie, na tym samym połączeniu musi odbywać się kilka wymian. W związku z tym uwierzytelnianie NTLM nie może być używane, jeśli pośredniczący serwer proxy nie obsługuje połączeń o zachowaniu aktywności. Uwierzytelnianie NTLM również kończy się niepowodzeniem, jeśli WinHttpSetOption jest używana z flagą WINHTTP_DISABLE_KEEP_ALIVE, która wyłącza semantyka zachowania aktywności.
Paszport Odpowiedź na żądanie Używa Microsoft Passport 1.4.
Negocjować Odpowiedź na żądanie Jeśli zarówno serwer, jak i klient korzystają z systemu Windows 2000 lub nowszego, używane jest uwierzytelnianie Kerberos. W przeciwnym razie jest używane uwierzytelnianie NTLM. Protokół Kerberos jest dostępny w systemach operacyjnych Windows 2000 i nowszych oraz jest uważany za bardziej bezpieczny niż uwierzytelnianie NTLM. Aby uwierzytelnianie negocjowane działało poprawnie, na tym samym połączeniu musi odbywać się kilka wymian. W związku z tym uwierzytelnianie negocjowane nie może być używane, jeśli pośredniczący serwer proxy nie obsługuje połączeń o zachowaniu aktywności. Uwierzytelnianie negocjowane również kończy się niepowodzeniem, jeśli WinHttpSetOption jest używana z flagą WINHTTP_DISABLE_KEEP_ALIVE, która wyłącza semantyka zachowania aktywności. Schemat uwierzytelniania Negotiate jest czasami nazywany zintegrowanym uwierzytelnianiem systemu Windows.

 

Uwierzytelnianie w aplikacjach WinHTTP

Interfejs programowania aplikacji WinHTTP (API) udostępnia dwie funkcje używane do uzyskiwania dostępu do zasobów internetowych w sytuacjach, w których wymagane jest uwierzytelnianie: WinHttpSetCredentials i WinHttpQueryAuthSchemes.

Gdy odpowiedź zostanie odebrana z kodem stanu 401 lub 407, WinHttpQueryAuthSchemes może służyć do analizowania nagłówków uwierzytelniania w celu określenia obsługiwanych schematów uwierzytelniania i celu uwierzytelniania. Elementem docelowym uwierzytelniania jest serwer lub serwer proxy, który żąda uwierzytelnienia. WinHttpQueryAuthSchemes określa również pierwszy schemat uwierzytelniania z dostępnych schematów na podstawie preferencji schematu uwierzytelniania sugerowanych przez serwer. Ta metoda wybierania schematu uwierzytelniania jest zachowaniem sugerowanym przez RFC 2616.

WinHttpSetCredentials umożliwia aplikacji określenie schematu uwierzytelniania używanego wraz z prawidłową nazwą użytkownika i hasłem do użycia na serwerze docelowym lub serwerze proxy. Po ustawieniu poświadczeń i ponownym wysłaniu żądania wymagane nagłówki są generowane i dodawane do żądania automatycznie. Ponieważ niektóre schematy uwierzytelniania wymagają wielu transakcji, WinHttpSendRequest może zwrócić błąd, ERROR_WINHTTP_RESEND_REQUEST. Po napotkaniu tego błędu aplikacja powinna kontynuować ponowne wysyłanie żądania do momentu odebrania odpowiedzi, która nie zawiera kodu stanu 401 lub 407. Kod stanu 200 wskazuje, że zasób jest dostępny i żądanie zakończy się pomyślnie. Zobacz kody stanu HTTP, aby uzyskać dodatkowe kody stanu, które można zwrócić.

Jeśli akceptowalny schemat uwierzytelniania i poświadczenia są znane przed wysłaniem żądania do serwera, aplikacja może wywołać WinHttpSetCredentials przed wywołaniem WinHttpSendRequest. W takim przypadku winHTTP próbuje wstępnie uwierzytelnić się z serwerem, podając poświadczenia lub dane uwierzytelniania w początkowym żądaniu do serwera. Wstępne uwierzytelnianie może zmniejszyć liczbę wymian w procesie uwierzytelniania i w związku z tym zwiększyć wydajność aplikacji.

Wstępne uwierzytelnianie może być używane z następującymi schematami uwierzytelniania:

  • Podstawowa — zawsze możliwa.
  • Negocjowanie rozwiązywania w protokole Kerberos — bardzo prawdopodobne; jedynym wyjątkiem jest, gdy niesymetryczność czasu między klientem a kontrolerem domeny.
  • (Negocjuj rozpoznawanie w NTLM) — nigdy nie jest to możliwe.
  • NTLM — możliwe tylko w systemie Windows Server 2008 R2.
  • Szyfruj — nigdy nie jest to możliwe.
  • Paszport — nigdy nie jest to możliwe; po początkowej odpowiedzi na żądanie winHTTP używa plików cookie do wstępnego uwierzytelniania w usłudze Passport.

Typowa aplikacja WinHTTP wykonuje następujące kroki w celu obsługi uwierzytelniania.

  • Zażądaj zasobu za pomocą WinHttpOpenRequest i WinHttpSendRequest.
  • Sprawdź nagłówki odpowiedzi za pomocą WinHttpQueryHeaders.
  • Jeśli zostanie zwrócony kod stanu 401 lub 407 wskazujący, że wymagane jest uwierzytelnianie, wywołaj metodę WinHttpQueryAuthSchemes, aby znaleźć akceptowalny schemat.
  • Ustaw schemat uwierzytelniania, nazwę użytkownika i hasło za pomocą WinHttpSetCredentials.
  • Wyślij ponownie żądanie przy użyciu tego samego dojścia żądania, wywołując WinHttpSendRequest.

Poświadczenia ustawione przez WinHttpSetCredentials są używane tylko dla jednego żądania. WinHTTP nie buforuje poświadczeń do użycia w innych żądaniach, co oznacza, że aplikacje muszą być napisane, które mogą odpowiadać na wiele żądań. Jeśli uwierzytelnione połączenie jest ponownie używane, inne żądania mogą nie zostać zakwestionowane, ale kod powinien być w stanie w dowolnym momencie odpowiedzieć na żądanie.

Przykład: pobieranie dokumentu

Poniższy przykładowy kod próbuje pobrać określony dokument z serwera HTTP. Kod stanu jest pobierany z odpowiedzi, aby określić, czy wymagane jest uwierzytelnianie. Jeśli zostanie znaleziony kod stanu 200, dokument jest dostępny. Jeśli zostanie znaleziony kod stanu 401 lub 407, wymagane jest uwierzytelnienie przed pobraniem dokumentu. W przypadku dowolnego innego kodu stanu zostanie wyświetlony komunikat o błędzie. Aby uzyskać listę możliwych kodów stanu, zobacz kody stanu HTTP.

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

Zasady automatycznego logowania

Zasady automatycznego logowania (automatycznego logowania) określają, kiedy dopuszczalne jest uwzględnienie poświadczeń domyślnych w żądaniu przez winHTTP. Poświadczenia domyślne to bieżący token wątku lub token sesji w zależności od tego, czy winHTTP jest używany w trybie synchronicznym, czy asynchronicznym. Token wątku jest używany w trybie synchronicznym, a token sesji jest używany w trybie asynchronicznym. Te poświadczenia domyślne są często nazwą użytkownika i hasłem używanym do logowania się do systemu Microsoft Windows.

Zaimplementowano zasady automatycznego logowania, aby zapobiec przypadkowemu użyciu tych poświadczeń do uwierzytelniania na niezaufanym serwerze. Domyślnie poziom zabezpieczeń jest ustawiony na WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, co umożliwia używanie poświadczeń domyślnych tylko dla żądań intranetowych. Zasady automatycznego logowania dotyczą tylko schematów uwierzytelniania NTLM i Negotiate. Poświadczenia nigdy nie są automatycznie przesyłane z innymi schematami.

Zasady automatycznego logowania można ustawić przy użyciu funkcji WinHttpSetOption z flagą WINHTTP_OPTION_AUTOLOGON_POLICY. Ta flaga ma zastosowanie tylko do dojścia żądania. Po ustawieniu zasad na WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW można wysyłać domyślne poświadczenia do wszystkich serwerów. Jeśli zasady są ustawione na WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH, do uwierzytelniania nie można używać poświadczeń domyślnych. Zdecydowanie zaleca się użycie automatycznego logowania na poziomie MEDIUM.

Przechowywane nazwy użytkowników i hasła

System Windows XP wprowadził koncepcję przechowywanych nazw użytkowników i haseł. Jeśli poświadczenia usługi Passport użytkownika są zapisywane za pośrednictwem Kreatora rejestracji usługi Passport lub standardowego okna dialogowego poświadczeń , jest on zapisywany w przechowywanych nazwach użytkowników i hasłach. W przypadku korzystania z winHTTP w systemie Windows XP lub nowszym usługa WinHTTP automatycznie używa poświadczeń przechowywanych nazw użytkowników i haseł, jeśli poświadczenia nie są jawnie ustawione. Jest to podobne do obsługi domyślnych poświadczeń logowania dla PROTOKOŁU NTLM/Kerberos. Jednak użycie domyślnych poświadczeń usługi Passport nie podlega ustawieniom zasad automatycznego logowania.