Dela via


Autentisering i WinHTTP

Vissa HTTP-servrar och proxyservrar kräver autentisering innan åtkomst till resurser på Internet tillåts. Funktionerna Microsoft Windows HTTP Services (WinHTTP) stöder server- och proxyautentisering för HTTP-sessioner.

Om HTTP-autentisering

Om autentisering krävs får HTTP-programmet statuskoden 401 (servern kräver autentisering) eller 407 (proxy kräver autentisering). Tillsammans med statuskoden skickar proxyn eller servern en eller flera autentiserande huvuden: WWW-Authenticate (för serverautentisering) eller Proxy-Authenticate (för proxyautentisering).

Varje autentiseringshuvud innehåller ett autentiseringsschema som stöds och, för scheman grundläggande och sammanfattande, en sfär. Om flera autentiseringsscheman stöds returnerar servern flera autentiseringshuvuden. Sfärvärdet är skiftlägeskänsligt och definierar en uppsättning servrar eller proxyservrar som samma autentiseringsuppgifter godkänns för. Till exempel kan rubriken "WWW-Authenticate: Basic Realm="example"" returneras när serverautentisering krävs. Det här huvudet anger att användarautentiseringsuppgifter måste anges för "exempel"-domänen.

Ett HTTP-program kan innehålla ett auktoriseringshuvudfält med en begäran som skickas till servern. Auktoriseringshuvudet innehåller autentiseringsschemat och det lämpliga svar som krävs av det schemat. Till exempel skulle rubriken "Authorization: Basic <username:password>" läggas till i begäran och skickas till servern om klienten fick svarshuvudet "WWW-Authenticate: Basic Realm="example".

Not

Även om de visas här som oformaterad text är användarnamnet och lösenordet faktiskt base64-kodade.

 

Det finns två allmänna typer av autentiseringsscheman:

  • Grundläggande autentiseringsschema, där användarnamnet och lösenordet skickas i klartext till servern.

    Det grundläggande autentiseringsschemat baseras på den modell som en klient måste identifiera sig med ett användarnamn och lösenord för varje sfär. Servern skickar endast begäran om begäran skickas med ett auktoriseringshuvud som innehåller ett giltigt användarnamn och lösenord.

  • Utmaningssvarsscheman, till exempel Kerberos, där servern utmanar klienten med autentiseringsdata. Klienten transformerar data med autentiseringsuppgifterna och skickar tillbaka transformerade data till servern för autentisering.

    Scheman för utmaningssvar möjliggör en säkrare autentisering. I ett schema för utmaningssvar överförs aldrig användarnamnet och lösenordet över nätverket. När klienten har valt ett schema för utmaningssvar returnerar servern en lämplig statuskod med en utmaning som innehåller autentiseringsdata för det schemat. Klienten skickar sedan begäran igen med rätt svar för att hämta den begärda tjänsten. Det kan ta flera utbyten att slutföras.

Följande tabell innehåller de autentiseringsscheman som stöds av WinHTTP, autentiseringstypen och en beskrivning av schemat.

Schema Typ Beskrivning
Grundläggande (klartext) Grundläggande Använder en base64-kodad sträng som innehåller användarnamnet och lösenordet.
Smälta Utmaningssvar Utmaningar med att använda ett nonce-värde (en server angiven datasträng). Ett giltigt svar innehåller en kontrollsumma för användarnamnet, lösenordet, det angivna nonce-värdet, HTTP-verbetoch den begärda URI:n (Uniform Resource Identifier).
NTLM Utmaningssvar Kräver att autentiseringsdata transformeras med användarautentiseringsuppgifterna för att bevisa identitet. För att NTLM-autentisering ska fungera korrekt måste flera utbyten ske på samma anslutning. Därför kan inte NTLM-autentisering användas om en mellanliggande proxy inte stöder keep-alive-anslutningar. NTLM-autentisering misslyckas också om WinHttpSetOption används med flaggan WINHTTP_DISABLE_KEEP_ALIVE som inaktiverar keep-alive-semantik.
Pass Utmaningssvar Använder Microsoft Passport 1.4.
Förhandla Utmaningssvar Om både servern och klienten använder Windows 2000 eller senare används Kerberos-autentisering. Annars används NTLM-autentisering. Kerberos är tillgängligt i Windows 2000 och senare operativsystem och anses vara säkrare än NTLM-autentisering. För att Negotiate-autentiseringen ska fungera korrekt måste flera utbyten ske på samma anslutning. Därför kan inte Negotiate-autentisering användas om en mellanliggande proxy inte stöder keep-alive-anslutningar. Förhandla om autentisering misslyckas också om WinHttpSetOption används med flaggan WINHTTP_DISABLE_KEEP_ALIVE som inaktiverar keep-alive-semantik. Negotiate-autentiseringsschemat kallas ibland integrerad Windows-autentisering.

 

Autentisering i WinHTTP-program

WinHTTP-programmets programmeringsgränssnitt (API) innehåller två funktioner som används för att komma åt Internetresurser i situationer där autentisering krävs: WinHttpSetCredentials och WinHttpQueryAuthSchemes.

När ett svar tas emot med statuskoden 401 eller 407 kan WinHttpQueryAuthSchemes användas för att parsa autentiseringshuvudena för att fastställa vilka autentiseringsscheman som stöds och autentiseringsmålet. Autentiseringsmålet är den server eller proxy som begär autentisering. WinHttpQueryAuthSchemes också avgör det första autentiseringsschemat, från de tillgängliga schemana, baserat på de autentiseringsschemainställningar som föreslås av servern. Den här metoden för att välja ett autentiseringsschema är det beteende som föreslås av RFC 2616.

WinHttpSetCredentials gör det möjligt för ett program att ange det autentiseringsschema som används tillsammans med ett giltigt användarnamn och lösenord för användning på målservern eller proxyn. När du har angett autentiseringsuppgifterna och överfört begäran igen genereras de nödvändiga rubrikerna och läggs till i begäran automatiskt. Eftersom vissa autentiseringsscheman kräver flera transaktioner WinHttpSendRequest kan returnera felet, ERROR_WINHTTP_RESEND_REQUEST. När det här felet påträffas bör programmet fortsätta att skicka begäran igen tills ett svar tas emot som inte innehåller statuskoden 401 eller 407. En statuskod för 200 anger att resursen är tillgänglig och att begäran lyckas. Se HTTP-statuskoder för ytterligare statuskoder som kan returneras.

Om ett acceptabelt autentiseringsschema och autentiseringsuppgifter är kända innan en begäran skickas till servern kan ett program anropa WinHttpSetCredentials innan WinHttpSendRequestanropas. I det här fallet försöker WinHTTP förautentisering med servern genom att ange autentiseringsuppgifter eller autentiseringsdata i den första begäran till servern. Förautentisering kan minska antalet utbyten i autentiseringsprocessen och därmed förbättra programmets prestanda.

Förautentisering kan användas med följande autentiseringsscheman:

  • Basic – alltid möjligt.
  • Förhandla om att lösa med Kerberos – mycket sannolikt möjligt; det enda undantaget är när tidsförskjutningarna är avstängda mellan klienten och domänkontrollanten.
  • (Förhandla om att lösa i NTLM) – aldrig möjligt.
  • NTLM – endast möjligt i Windows Server 2008 R2.
  • Sammandrag – aldrig möjligt.
  • Passport - aldrig möjligt; Efter det första utmaningssvaret använder WinHTTP cookies för att förautentisera till Passport.

Ett typiskt WinHTTP-program slutför följande steg för att hantera autentisering.

Autentiseringsuppgifterna som anges av WinHttpSetCredentials används bara för en begäran. WinHTTP cachelagrar inte de autentiseringsuppgifter som ska användas i andra begäranden, vilket innebär att program måste skrivas som kan svara på flera begäranden. Om en autentiserad anslutning återanvänds kan det hända att andra begäranden inte utmanas, men koden bör kunna svara på en begäran när som helst.

Exempel: Hämta ett dokument

Följande exempelkod försöker hämta ett angivet dokument från en HTTP-server. Statuskoden hämtas från svaret för att avgöra om autentisering krävs. Om en 200-statuskod hittas är dokumentet tillgängligt. Om en statuskod på 401 eller 407 hittas krävs autentisering innan dokumentet kan hämtas. För annan statuskod visas ett felmeddelande. En lista över möjliga statuskoder finns i HTTP-statuskoder.

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

Princip för automatisk inloggning

Principen för automatisk inloggning (automatisk inloggning) avgör när det är acceptabelt för WinHTTP att inkludera standardautentiseringsuppgifterna i en begäran. Standardautentiseringsuppgifterna är antingen den aktuella trådtoken eller sessionstoken beroende på om WinHTTP används i synkront eller asynkront läge. Trådtoken används i synkront läge och sessionstoken används i asynkront läge. Dessa standardautentiseringsuppgifter är ofta det användarnamn och lösenord som används för att logga in på Microsoft Windows.

Principen för automatisk inloggning implementerades för att förhindra att dessa autentiseringsuppgifter används tillfälligt för att autentisera mot en ej betrodd server. Som standard är säkerhetsnivån inställd på WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, vilket gör att standardautentiseringsuppgifterna endast kan användas för intranätbegäranden. Principen för automatisk inloggning gäller endast för NTLM- och Negotiate-autentiseringsscheman. Autentiseringsuppgifter överförs aldrig automatiskt med andra scheman.

Principen för automatisk inloggning kan anges med funktionen WinHttpSetOption med flaggan WINHTTP_OPTION_AUTOLOGON_POLICY. Den här flaggan gäller endast för begärandehandtaget. När principen är inställd på WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW kan standardautentiseringsuppgifter skickas till alla servrar. När principen är inställd på WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH kan standardautentiseringsuppgifter inte användas för autentisering. Vi rekommenderar starkt att du använder automatisk inloggning på MEDIUM-nivå.

Lagrade användarnamn och lösenord

Windows XP introducerade begreppet lagrade användarnamn och lösenord. Om en användares Passport-autentiseringsuppgifter sparas via guiden Passport-registrering eller standarddialogrutan autentiseringsuppgiftersparas den i lagrade användarnamn och lösenord. När du använder WinHTTP i Windows XP eller senare använder WinHTTP automatiskt autentiseringsuppgifterna i lagrade användarnamn och lösenord om autentiseringsuppgifterna inte uttryckligen anges. Detta liknar stödet för standardautentiseringsuppgifter för inloggning för NTLM/Kerberos. Användning av standardautentiseringsuppgifter för Passport omfattas dock inte av principinställningarna för automatisk inloggning.