Autenticazione in WinHTTP

Alcuni server HTTP e proxy richiedono l'autenticazione prima di consentire l'accesso alle risorse su Internet. Le funzioni Di Microsoft Windows HTTP Services (WinHTTP) supportano l'autenticazione del server e del proxy per le sessioni HTTP.

Informazioni sull'autenticazione HTTP

Se è necessaria l'autenticazione, l'applicazione HTTP riceve un codice di stato 401 (server richiede l'autenticazione) o 407 (il proxy richiede l'autenticazione). Insieme al codice di stato, il proxy o il server invia una o più intestazioni di autenticazione: WWW-Authenticate (per l'autenticazione del server) o Proxy-Authenticate (per l'autenticazione proxy).

Ogni intestazione di autenticazione contiene uno schema di autenticazione supportato e, per gli schemi Basic e Digest, un'area di autenticazione. Se sono supportati più schemi di autenticazione, il server restituisce più intestazioni di autenticazione. Il valore dell'area di autenticazione fa distinzione tra maiuscole e minuscole e definisce un set di server o proxy per cui vengono accettate le stesse credenziali. Ad esempio, l'intestazione "WWW-Authenticate: Basic Realm="example"" potrebbe essere restituita quando è necessaria l'autenticazione del server. Questa intestazione specifica che le credenziali utente devono essere fornite per il dominio "example".

Un'applicazione HTTP può includere un campo di intestazione dell'autorizzazione con una richiesta inviata al server. L'intestazione dell'autorizzazione contiene lo schema di autenticazione e la risposta appropriata richiesta da tale schema. Ad esempio, l'intestazione "Authorization: Basic <username:password>" viene aggiunta alla richiesta e inviata al server se il client ha ricevuto l'intestazione di risposta "WWW-Authenticate: Basic Realm="example"".

Nota

Anche se vengono visualizzati qui come testo normale, il nome utente e la password sono effettivamente codificati in base64.

 

Esistono due tipi generali di schemi di autenticazione:

  • Schema di autenticazione di base, in cui il nome utente e la password vengono inviati in testo non crittografato al server.

    Lo schema di autenticazione di base si basa sul modello che un client deve identificarsi con un nome utente e una password per ogni area di autenticazione. Il server esegue la richiesta solo se la richiesta viene inviata con un'intestazione di autorizzazione che include un nome utente e una password validi.

  • Schemi challenge-response, ad esempio Kerberos, in cui il server sfida il client con i dati di autenticazione. Il client trasforma i dati con le credenziali utente e invia i dati trasformati al server per l'autenticazione.

    Gli schemi challenge-response consentono un'autenticazione più sicura. In uno schema challenge-response, il nome utente e la password non vengono mai trasmessi in rete. Dopo che il client seleziona uno schema challenge-response, il server restituisce un codice di stato appropriato con una richiesta di verifica contenente i dati di autenticazione per tale schema. Il client invia quindi nuovamente la richiesta con la risposta corretta per ottenere il servizio richiesto. Gli schemi challenge-response possono portare a termine più scambi.

La tabella seguente contiene gli schemi di autenticazione supportati da WinHTTP, il tipo di autenticazione e una descrizione dello schema.

Schema Tipo Descrizione
Basic (testo non crittografato) Basic Usa una stringa con codifica Base64 che contiene il nome utente e la password.
Digest Risposta alla richiesta di verifica Problemi relativi all'uso di un valore nonce (stringa di dati specificata dal server). Una risposta valida contiene un checksum del nome utente, della password, del valore nonce specificato, del verbo HTTP e dell'URI (Uniform Resource Identifier) richiesto.
NTLM Risposta alla richiesta di verifica Richiede che i dati di autenticazione vengano trasformati con le credenziali utente per dimostrare l'identità. Affinché l'autenticazione NTLM funzioni correttamente, è necessario che vengano eseguite diverse scambi sulla stessa connessione. Pertanto, l'autenticazione NTLM non può essere utilizzata se un proxy intermedio non supporta le connessioni keep-alive. L'autenticazione NTLM ha esito negativo anche se WinHttpSetOption viene usato con il flag WINHTTP_DISABLE_KEEP_ALIVE che disabilita la semantica keep-alive.
Passport Risposta alla richiesta di verifica Usa Microsoft Passport 1.4.
Negotiate Risposta alla richiesta di verifica Se sia il server che il client usano Windows 2000 o versioni successive, viene usata l'autenticazione Kerberos. In caso contrario, viene utilizzata l'autenticazione NTLM. Kerberos è disponibile nei sistemi operativi Windows 2000 e versioni successive e viene considerato più sicuro rispetto all'autenticazione NTLM. Affinché l'autenticazione negotiate funzioni correttamente, è necessario che vengano eseguite diverse scambi sulla stessa connessione. Pertanto, non è possibile usare l'autenticazione Negotiate se un proxy intermedio non supporta le connessioni keep-alive. L'autenticazione negoziata ha esito negativo anche se WinHttpSetOption viene usato con il flag di WINHTTP_DISABLE_KEEP_ALIVE che disabilita la semantica keep-alive. Lo schema di autenticazione Negotiate è talvolta denominato integrated autenticazione di Windows.

 

Autenticazione nelle applicazioni WinHTTP

L'API (Application Programming Interface) WinHTTP fornisce due funzioni usate per accedere alle risorse Internet nelle situazioni in cui è necessaria l'autenticazione: WinHttpSetCredentials e WinHttpQueryAuthSchemes.

Quando viene ricevuta una risposta con un codice di stato 401 o 407, è possibile usare WinHttpQueryAuthSchemes per analizzare le intestazioni di autenticazione per determinare gli schemi di autenticazione supportati e la destinazione di autenticazione. La destinazione di autenticazione è il server o il proxy che richiede l'autenticazione. WinHttpQueryAuthSchemes determina anche il primo schema di autenticazione, dagli schemi disponibili, in base alle preferenze dello schema di autenticazione suggerite dal server. Questo metodo per la scelta di uno schema di autenticazione è il comportamento suggerito da RFC 2616.

WinHttpSetCredentials consente a un'applicazione di specificare lo schema di autenticazione usato insieme a un nome utente e una password validi per l'uso nel server di destinazione o nel proxy. Dopo aver impostato le credenziali e inviato di nuovo la richiesta, le intestazioni necessarie vengono generate e aggiunte automaticamente alla richiesta. Poiché alcuni schemi di autenticazione richiedono più transazioni WinHttpSendRequest potrebbe restituire l'errore, ERROR_WINHTTP_RESEND_REQUEST. Quando si verifica questo errore, l'applicazione deve continuare a inviare nuovamente la richiesta finché non viene ricevuta una risposta che non contiene un codice di stato 401 o 407. Un codice di stato 200 indica che la risorsa è disponibile e la richiesta ha esito positivo. Per altri codici di stato che possono essere restituiti, vedere Codici di stato HTTP .

Se uno schema di autenticazione e le credenziali accettabili sono noti prima dell'invio di una richiesta al server, un'applicazione può chiamare WinHttpSetCredentials prima di chiamare WinHttpSendRequest. In questo caso, WinHTTP tenta di eseguire la preautenticazione con il server fornendo credenziali o dati di autenticazione nella richiesta iniziale al server. La preautenticazione può ridurre il numero di scambi nel processo di autenticazione e quindi migliorare le prestazioni dell'applicazione.

La preautenticazione può essere usata con gli schemi di autenticazione seguenti:

  • Basic: sempre possibile.
  • Negoziare la risoluzione in Kerberos - molto probabilmente possibile; l'unica eccezione è quando gli sfasamenti temporali sono disattivati tra il client e il controller di dominio.
  • (Negoziare la risoluzione in NTLM): non è mai possibile.
  • NTLM: possibile solo in Windows Server 2008 R2.
  • Digest - mai possibile.
  • Passport - mai possibile; dopo la richiesta di verifica iniziale, WinHTTP usa i cookie per eseguire l'autenticazione preliminare in Passport.

Un'applicazione WinHTTP tipica completa i passaggi seguenti per gestire l'autenticazione.

Le credenziali impostate da WinHttpSetCredentials vengono usate solo per una richiesta. WinHTTP non memorizza nella cache le credenziali da usare in altre richieste, il che significa che le applicazioni devono essere scritte che possono rispondere a più richieste. Se viene riutilizzata una connessione autenticata, è possibile che altre richieste non vengano richieste, ma il codice deve essere in grado di rispondere a una richiesta in qualsiasi momento.

Esempio: Recupero di un documento

Il codice di esempio seguente tenta di recuperare un documento specificato da un server HTTP. Il codice di stato viene recuperato dalla risposta per determinare se è necessaria l'autenticazione. Se viene trovato un codice di stato 200, il documento è disponibile. Se viene trovato un codice di stato 401 o 407, è necessaria l'autenticazione prima che il documento possa essere recuperato. Per qualsiasi altro codice di stato, viene visualizzato un messaggio di errore. Per un elenco dei codici di stato possibili, vedere Codici di stato 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 );
}

Criteri di accesso automatico

I criteri di accesso automatico (accesso automatico) determinano quando è accettabile che WinHTTP includa le credenziali predefinite in una richiesta. Le credenziali predefinite sono il token thread corrente o il token di sessione a seconda che WinHTTP venga usato in modalità sincrona o asincrona. Il token del thread viene usato in modalità sincrona e il token di sessione viene usato in modalità asincrona. Queste credenziali predefinite sono spesso il nome utente e la password usati per accedere a Microsoft Windows.

I criteri di accesso automatico sono stati implementati per impedire l'uso casuale di queste credenziali per l'autenticazione in un server non attendibile. Per impostazione predefinita, il livello di sicurezza è impostato su WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, che consente di usare le credenziali predefinite solo per le richieste Intranet. I criteri di accesso automatico si applicano solo agli schemi di autenticazione NTLM e Negotiate. Le credenziali non vengono mai trasmesse automaticamente con altri schemi.

I criteri di accesso automatico possono essere impostati usando la funzione WinHttpSetOption con il flag WINHTTP_OPTION_AUTOLOGON_POLICY . Questo flag si applica solo all'handle della richiesta. Quando il criterio è impostato su WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW, le credenziali predefinite possono essere inviate a tutti i server. Quando il criterio è impostato su WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH, non è possibile usare le credenziali predefinite per l'autenticazione. È consigliabile usare l'accesso automatico a livello MEDIUM.

Gestione nomi utente e password archiviati

Windows XP ha introdotto il concetto di nomi utente e password archiviati. Se le credenziali passport di un utente vengono salvate tramite la Registrazione guidata passport o la finestra di dialogo credenziali standard, viene salvata nei nomi utente e nelle password archiviati. Quando si usa WinHTTP in Windows XP o versione successiva, WinHTTP usa automaticamente le credenziali nei nomi utente e nelle password archiviate se le credenziali non sono impostate in modo esplicito. È simile al supporto delle credenziali di accesso predefinite per NTLM/Kerberos. Tuttavia, l'uso delle credenziali di Passport predefinite non è soggetto alle impostazioni dei criteri di accesso automatico.