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, erhält die HTTP-Anwendung den Statuscode 401 (Server erfordert Authentifizierung) oder 407 (Proxy erfordert Authentifizierung). Zusammen mit dem Statuscode 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 es wird ein Satz von Servern oder Proxys definiert, für die die gleichen Anmeldeinformationen akzeptiert werden. Beispielsweise kann der Header "WWW-Authenticate: Basic Realm="example" zurückgegeben werden, wenn eine Serverauthentifizierung erforderlich ist. Dieser Header gibt an, dass Benutzeranmeldeinformationen für die Domäne "example" 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 würde 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" erhalten hat.

Hinweis

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

Es gibt zwei allgemeine Arten von Authentifizierungsschemas:

  • Standardauthentifizierungsschema, bei dem Benutzername und Kennwort als Klartext an den Server gesendet werden.

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

  • Abfrageantwortschemas wie Kerberos, in denen der Server den Client mit Authentifizierungsdatenherausfordert. Der Client transformiert die Daten mit den Benutzeranmeldeinformationen und sendet die transformierten Daten zur Authentifizierung zurück an den Server.

    Abfrage-/Antwortschemas ermöglichen eine sicherere Authentifizierung. In einem Abfrage-Antwort-Schema werden Benutzername und Kennwort nie über das Netzwerk übertragen. Nachdem der Client ein Abfrage-Antwort-Schema ausgewählt hat, gibt der Server einen entsprechenden Statuscode mit einer Abfrage zurück, die die Authentifizierungsdaten für dieses Schema enthält. Der Client senden die Anforderung dann erneut mit der richtigen Antwort, um den angeforderten Dienst abzurufen. Abfrage-/Antwortschemas können mehrere Austauschvorgänge dauern.

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 Abfrageantwort Herausforderungen bei der Verwendung eines Nonce-Werts (einer vom Server angegebenen Datenzeichenfolge). Eine gültige Antwort enthält eine Prüfsumme des Benutzernamens, des Kennworts, des angegebenen Nonce-Werts, des HTTP-Verbsund des angeforderten Uniform Resource Identifier (URI).
NTLM Abfrageantwort 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 für 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 die Keep-Alive-Semantik deaktiviert.
Passport Abfrageantwort Verwendet Microsoft Passport 1.4.
Aushandeln Abfrageantwort 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 in Windows Betriebssystemen 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 für dieselbe Verbindung erfolgen. Daher kann die Negotiate-Authentifizierung nicht verwendet werden, wenn ein dazwischen liegender Proxy keine Keep-Alive-Verbindungen unterstützt. Die Negotiate-Authentifizierung schlägt auch fehl, wenn WinHttpSetOption mit dem WINHTTP _ DISABLE KEEP _ _ ALIVE-Flag verwendet wird, das die Keep-Alive-Semantik deaktiviert. Das Negotiate-Authentifizierungsschema wird manchmal als integrierte Windows-Authentifizierung bezeichnet.

Authentifizierung in WinHTTP-Anwendungen

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

Wenn eine Antwort mit dem Statuscode 401 oder 407 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 2616vorgeschlagene Verhalten.

WinHttpSetCredentials ermöglicht 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 ERROR _ WINHTTP _ RESEND _ REQUEST zurückgeben. Wenn dieser Fehler auftritt, sollte die Anwendung die Anforderung so lange erneut senden, bis eine Antwort empfangen wird, die keinen Statuscode 401 oder 407 enthält. Der Statuscode 200 gibt an, dass die Ressource verfügbar ist und die Anforderung erfolgreich war. Weitere Statuscodes, 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 WinHttpSendRequestaufgerufen wird. In diesem Fall versucht WinHTTP die Vorauthentifizierung mit dem Server, indem anmeldeinformationen oder Authentifizierungsdaten in der ersten Anforderung an den Server angegeben 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.
  • Aushandeln der Auflösung in Kerberos – sehr wahrscheinlich möglich; Die einzige Ausnahme ist, wenn die Zeitabweichungen zwischen dem Client und dem Domänencontroller deaktiviert sind.
  • (Auflösen in NTLM aushandeln) – nie möglich.
  • NTLM: nur in Windows Server 2008 R2 möglich.
  • Digest: nie möglich.
  • Passport – nie möglich; Nach der ersten Abfrageantwort verwendet WinHTTP Cookies für die Vorabauthentifizierung bei Passport.

Eine typische WinHTTP-Anwendung schließt die folgenden Schritte ab, 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 erneut verwendet wird, werden möglicherweise keine anderen Anforderungen angefordert, aber Ihr Code sollte jederzeit auf eine Anforderung reagieren können.

Beispiel: Abrufen eines Dokuments

Der folgende Beispielcode versucht, ein angegebenes Dokument von einem HTTP-Server abzurufen. Der Statuscode wird aus der Antwort abgerufen, um zu bestimmen, ob eine Authentifizierung erforderlich ist. Wenn ein 200-Statuscode gefunden wird, ist das Dokument verfügbar. Wenn der Statuscode 401 oder 407 gefunden wird, ist eine Authentifizierung erforderlich, bevor das Dokument abgerufen werden kann. Für jeden anderen Statuscode wird eine Fehlermeldung angezeigt. Eine Liste der möglichen Statuscodes 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 die automatische Anmeldung (automatische Anmeldung) bestimmt, wann winHTTP die Standardanmeldeinformationen in eine Anforderung aufnehmen darf. 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 und das Sitzungstoken im asynchronen Modus verwendet. Bei diesen Standardanmeldeinformationen handelt es sich häufig um den Benutzernamen 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 für die Authentifizierung bei einem nicht vertrauenswürdigen Server verwendet werden. Standardmäßig ist die Sicherheitsstufe auf WINHTTP _ AUTOLOGON _ SECURITY LEVEL MEDIUM _ _ festgelegt, wodurch 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 keine Standardanmeldeinformationen für die Authentifizierung verwendet werden. Es wird dringend empfohlen, die automatische Anmeldung auf der Ebene MEDIUM zu verwenden.

Gespeicherte Benutzernamen und Kennwörter

Windows XP hat das Konzept von gespeicherten Benutzernamen und Kennwörtern eingeführt. Wenn die Passport-Anmeldeinformationen eines Benutzers über den Passport-Registrierungs-Assistenten oder das Standarddialogfeld für Anmeldeinformationen gespeichert werden, werden sie in den gespeicherten Benutzernamen und Kennwörtern gespeichert. Wenn WinHTTP auf 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 von Passport-Standardanmeldeinformationen unterliegt jedoch nicht den Einstellungen für automatische Anmelderichtlinien.