Authentifizierung in WinHTTP

Einige HTTP-Server und Proxys erfordern eine Authentifizierung, bevor der Zugriff auf Ressourcen im Internet zugelassen wird. Die WinHTTP-Funktionen (Microsoft Windows HTTP Services) unterstützen die Server- und Proxyauthentifizierung für HTTP-Sitzungen.

Informationen zur HTTP-Authentifizierung

Wenn eine Authentifizierung erforderlich ist, empfängt die HTTP-Anwendung den status Code 401 (Server erfordert Authentifizierung) oder 407 (Proxy erfordert Authentifizierung). Zusammen mit dem status Code sendet der Proxy oder Server einen oder mehrere Authentifizierungsheader: WWW-Authenticate (für die Serverauthentifizierung) oder Proxy-Authenticate (für die Proxyauthentifizierung).

Jeder Authentifizierungsheader enthält ein unterstütztes Authentifizierungsschema und für die Schemas "Basic" und "Digest" einen Bereich. Wenn mehrere Authentifizierungsschemas unterstützt werden, gibt der Server mehrere Authentifizierungsheader zurück. Beim Bereichswert wird die Groß-/Kleinschreibung beachtet und eine Gruppe von Servern oder Proxys definiert, für die dieselben Anmeldeinformationen akzeptiert werden. Beispielsweise kann der Header "WWW-Authenticate: Basic Realm="example"" zurückgegeben werden, wenn die Serverauthentifizierung erforderlich ist. Dieser Header gibt an, dass Benutzeranmeldeinformationen für die Beispieldomäne angegeben werden müssen.

Eine HTTP-Anwendung kann ein Autorisierungsheaderfeld mit einer Anforderung enthalten, die sie an den Server sendet. Der Autorisierungsheader enthält das Authentifizierungsschema und die entsprechende Antwort, die für dieses Schema erforderlich ist. Beispielsweise wird der Header "Authorization: Basic <username:password>" der Anforderung hinzugefügt und an den Server gesendet, wenn der Client den Antwortheader "WWW-Authenticate: Basic Realm="example"" empfangen hat.

Hinweis

Obwohl sie hier als Nur-Text angezeigt werden, sind Benutzername und Kennwort tatsächlich base64-codiert.

 

Es gibt zwei allgemeine Arten von Authentifizierungsschemas:

  • Standardauthentifizierungsschema, bei dem der Benutzername und das Kennwort in Klartext an den Server gesendet werden.

    Das Standardauthentifizierungsschema basiert auf dem Modell, das ein Client mit einem Benutzernamen und kennwort für jeden Bereich identifizieren muss. Der Server verarbeitet die Anforderung nur, wenn die Anforderung mit einem Autorisierungsheader gesendet wird, der einen gültigen Benutzernamen und ein gültiges Kennwort enthält.

  • Challenge-Response-Schemas wie Kerberos, bei denen der Server den Client mit Authentifizierungsdaten herausfordert. Der Client transformiert die Daten mit den Benutzeranmeldeinformationen und sendet die transformierten Daten zur Authentifizierung zurück an den Server.

    Challenge-Response-Schemas ermöglichen eine sicherere Authentifizierung. In einem Challenge-Response-Schema werden der Benutzername und das Kennwort nie über das Netzwerk übertragen. Nachdem der Client ein Challenge-Antwort-Schema ausgewählt hat, gibt der Server einen geeigneten status Code mit einer Challenge zurück, die die Authentifizierungsdaten für dieses Schema enthält. Der Client sendet die Anforderung dann erneut mit der richtigen Antwort, um den angeforderten Dienst abzurufen. Herausforderungsreaktionsschemas können mehrere Austauschvorgänge durchführen.

Die folgende Tabelle enthält die von WinHTTP unterstützten Authentifizierungsschemas, den Authentifizierungstyp und eine Beschreibung des Schemas.

Schema type BESCHREIBUNG
Basic (Klartext) Basic Verwendet eine base64-codierte Zeichenfolge, die den Benutzernamen und das Kennwort enthält.
Digest Challenge-Response Fordert die Verwendung eines Noncewerts (eine vom Server angegebene Datenzeichenfolge) auf. Eine gültige Antwort enthält eine Prüfsumme des Benutzernamens, des Kennworts, des angegebenen Noncewerts, des HTTP-Verbs und des angeforderten URI (Uniform Resource Identifier).
NTLM Challenge-Response Erfordert, dass die Authentifizierungsdaten mit den Benutzeranmeldeinformationen transformiert werden, um die Identität nachzuweisen. Damit die NTLM-Authentifizierung ordnungsgemäß funktioniert, müssen mehrere Austauschvorgänge über dieselbe Verbindung erfolgen. Daher kann die NTLM-Authentifizierung nicht verwendet werden, wenn ein dazwischen liegender Proxy keine Keep-Alive-Verbindungen unterstützt. Die NTLM-Authentifizierung schlägt auch fehl, wenn WinHttpSetOption mit dem WINHTTP_DISABLE_KEEP_ALIVE-Flag verwendet wird, das keep-alive-Semantik deaktiviert.
Passport Challenge-Response Verwendet Microsoft Passport 1.4.
Aushandeln Challenge-Response Wenn sowohl der Server als auch der Client Windows 2000 oder höher verwenden, wird die Kerberos-Authentifizierung verwendet. Andernfalls wird die NTLM-Authentifizierung verwendet. Kerberos ist unter Windows 2000 und höher verfügbar und gilt als sicherer als die NTLM-Authentifizierung. Damit die Negotiate-Authentifizierung ordnungsgemäß funktioniert, müssen mehrere Austauschvorgänge über dieselbe Verbindung erfolgen. Daher kann die Negotiate-Authentifizierung nicht verwendet werden, wenn ein dazwischen liegender Proxy keine Keep-Alive-Verbindungen unterstützt. Bei der Aushandlung der Authentifizierung tritt auch ein Fehler auf, wenn WinHttpSetOption mit dem WINHTTP_DISABLE_KEEP_ALIVE-Flag verwendet wird, das keep-alive-Semantik deaktiviert. Das Negotiate-Authentifizierungsschema wird manchmal als integrierte Windows-Authentifizierung bezeichnet.

 

Authentifizierung in WinHTTP-Anwendungen

Die WinHTTP-API (Application Programming Interface) stellt zwei Funktionen bereit, die für den Zugriff auf Internetressourcen in Situationen verwendet werden, in denen eine Authentifizierung erforderlich ist: WinHttpSetCredentials und WinHttpQueryAuthSchemes.

Wenn eine Antwort mit dem Code 401 oder 407 status empfangen wird, kann WinHttpQueryAuthSchemes verwendet werden, um die Authentifizierungsheader zu analysieren, um die unterstützten Authentifizierungsschemas und das Authentifizierungsziel zu bestimmen. Das Authentifizierungsziel ist der Server oder Proxy, der die Authentifizierung anfordert. WinHttpQueryAuthSchemes bestimmt auch das erste Authentifizierungsschema anhand der verfügbaren Schemas basierend auf den vom Server vorgeschlagenen Authentifizierungsschemaeinstellungen. Diese Methode zum Auswählen eines Authentifizierungsschemas ist das von RFC 2616 vorgeschlagene Verhalten.

WinHttpSetCredentials ermöglicht es einer Anwendung, das Authentifizierungsschema anzugeben, das zusammen mit einem gültigen Benutzernamen und Kennwort für die Verwendung auf dem Zielserver oder Proxy verwendet wird. Nach dem Festlegen der Anmeldeinformationen und dem erneuten Senden der Anforderung werden die erforderlichen Header generiert und der Anforderung automatisch hinzugefügt. Da einige Authentifizierungsschemas mehrere Transaktionen erfordern , kann WinHttpSendRequest den Fehler zurückgeben, ERROR_WINHTTP_RESEND_REQUEST. Wenn dieser Fehler auftritt, sollte die Anwendung die Anforderung weiterhin erneut senden, bis eine Antwort empfangen wird, die keinen 401- oder 407-status-Code enthält. Ein 200 status Code gibt an, dass die Ressource verfügbar ist und die Anforderung erfolgreich ist. Weitere status Codes, die zurückgegeben werden können, finden Sie unter HTTP-Statuscodes.

Wenn ein akzeptables Authentifizierungsschema und Anmeldeinformationen bekannt sind, bevor eine Anforderung an den Server gesendet wird, kann eine Anwendung WinHttpSetCredentials aufrufen, bevor WinHttpSendRequest aufgerufen wird. In diesem Fall versucht WinHTTP die Vorauthentifizierung beim Server, indem Anmeldeinformationen oder Authentifizierungsdaten in der anfänglichen Anforderung an den Server bereitgestellt werden. Die Vorauthentifizierung kann die Anzahl der Austauschvorgänge im Authentifizierungsprozess verringern und somit die Anwendungsleistung verbessern.

Die Vorauthentifizierung kann mit den folgenden Authentifizierungsschemas verwendet werden:

  • Basic – immer möglich.
  • Aushandlung der Auflösung in Kerberos - sehr wahrscheinlich möglich; die einzige Ausnahme ist, wenn die Zeitschiefe zwischen dem Client und dem Domänencontroller deaktiviert ist.
  • (Aushandlung der Auflösung in NTLM) – nie möglich.
  • NTLM: Nur in Windows Server 2008 R2 möglich.
  • Digest – nie möglich.
  • Reisepass - nie möglich; Nach der ersten Abfrageantwort verwendet WinHTTP Cookies, um sich bei Passport vorzu authentifizieren.

Eine typische WinHTTP-Anwendung führt die folgenden Schritte aus, um die Authentifizierung zu verarbeiten.

Die von WinHttpSetCredentials festgelegten Anmeldeinformationen werden nur für eine Anforderung verwendet. WinHTTP speichert die Anmeldeinformationen nicht zwischen, die in anderen Anforderungen verwendet werden sollen. Dies bedeutet, dass Anwendungen geschrieben werden müssen, die auf mehrere Anforderungen reagieren können. Wenn eine authentifizierte Verbindung wieder verwendet wird, werden andere Anforderungen möglicherweise nicht abgefragt, aber Ihr Code sollte jederzeit auf eine Anforderung reagieren können.

Beispiel: Abrufen eines Dokuments

Im folgenden Beispielcode wird versucht, ein angegebenes Dokument von einem HTTP-Server abzurufen. Der status Code wird aus der Antwort abgerufen, um zu bestimmen, ob eine Authentifizierung erforderlich ist. Wenn ein 200 status Code gefunden wird, ist das Dokument verfügbar. Wenn ein status Code 401 oder 407 gefunden wird, ist eine Authentifizierung erforderlich, bevor das Dokument abgerufen werden kann. Für alle anderen status Code wird eine Fehlermeldung angezeigt. Eine Liste möglicher status Codes finden Sie unter HTTP-Statuscodes.

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

Richtlinie für automatische Anmeldung

Die Richtlinie für automatische Anmeldung (automatische Anmeldung) bestimmt, wann es akzeptabel ist, dass WinHTTP die Standardanmeldeinformationen in eine Anforderung einschließt. Die Standardanmeldeinformationen sind entweder das aktuelle Threadtoken oder das Sitzungstoken, je nachdem, ob WinHTTP im synchronen oder asynchronen Modus verwendet wird. Das Threadtoken wird im synchronen Modus verwendet, und das Sitzungstoken wird im asynchronen Modus verwendet. Diese Standardanmeldeinformationen sind häufig der Benutzername und das Kennwort, die für die Anmeldung bei Microsoft Windows verwendet werden.

Die Richtlinie für die automatische Anmeldung wurde implementiert, um zu verhindern, dass diese Anmeldeinformationen gelegentlich zur Authentifizierung bei einem nicht vertrauenswürdigen Server verwendet werden. Standardmäßig ist die Sicherheitsstufe auf WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM festgelegt, sodass die Standardanmeldeinformationen nur für Intranetanforderungen verwendet werden können. Die Richtlinie für die automatische Anmeldung gilt nur für die Authentifizierungsschemas NTLM und Negotiate. Anmeldeinformationen werden nie automatisch mit anderen Schemas übertragen.

Die Richtlinie für die automatische Anmeldung kann mithilfe der WinHttpSetOption-Funktion mit dem WINHTTP_OPTION_AUTOLOGON_POLICY-Flag festgelegt werden. Dieses Flag gilt nur für das Anforderungshandle. Wenn die Richtlinie auf WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW festgelegt ist, können Standardanmeldeinformationen an alle Server gesendet werden. Wenn die Richtlinie auf WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH festgelegt ist, können die Standardanmeldeinformationen nicht für die Authentifizierung verwendet werden. Es wird dringend empfohlen, die automatische Anmeldung auf mittlerer Ebene zu verwenden.

Gespeicherte Benutzernamen und Kennwörter

Windows XP hat das Konzept gespeicherter Benutzernamen und Kennwörter eingeführt. Wenn die Passport-Anmeldeinformationen eines Benutzers über den Passport-Registrierungs-Assistenten oder das Standarddialogfeld für Anmeldeinformationen gespeichert werden, werden sie unter Gespeicherte Benutzernamen und Kennwörter gespeichert. Wenn WinHTTP unter Windows XP oder höher verwendet wird, verwendet WinHTTP automatisch die Anmeldeinformationen in den gespeicherten Benutzernamen und Kennwörtern, wenn die Anmeldeinformationen nicht explizit festgelegt sind. Dies ähnelt der Unterstützung von Standardanmeldeinformationen für NTLM/Kerberos. Die Verwendung der Standardmäßigen Passport-Anmeldeinformationen unterliegt jedoch nicht den Richtlinieneinstellungen für die automatische Anmeldung.