Authentification dans WinHTTP

Certains serveurs et proxys HTTP nécessitent une authentification avant d’autoriser l’accès aux ressources sur Internet. Les fonctions Microsoft Windows HTTP Services (WinHTTP) prennent en charge l’authentification du serveur et du proxy pour les sessions HTTP.

À propos de l’authentification HTTP

Si l’authentification est requise, l’application HTTP reçoit un code status 401 (le serveur nécessite une authentification) ou 407 (le proxy nécessite une authentification). Avec le code status, le proxy ou le serveur envoie un ou plusieurs en-têtes d’authentification : WWW-Authenticate (pour l’authentification du serveur) ou Proxy-Authenticate (pour l’authentification proxy).

Chaque en-tête d’authentification contient un schéma d’authentification pris en charge et, pour les schémas De base et Digest, un domaine. Si plusieurs schémas d’authentification sont pris en charge, le serveur retourne plusieurs en-têtes d’authentification. La valeur du domaine respecte la casse et définit un ensemble de serveurs ou de proxys pour lesquels les mêmes informations d’identification sont acceptées. Par exemple, l’en-tête « WWW-Authenticate: Basic Realm=»example » peut être retourné lorsque l’authentification du serveur est requise. Cet en-tête spécifie que les informations d’identification de l’utilisateur doivent être fournies pour le domaine « example ».

Une application HTTP peut inclure un champ d’en-tête d’autorisation avec une requête qu’elle envoie au serveur. L’en-tête d’autorisation contient le schéma d’authentification et la réponse appropriée requise par ce schéma. Par exemple, l’en-tête « Authorization: Basic <username:password> » est ajouté à la demande et envoyé au serveur si le client a reçu l’en-tête de réponse « WWW-Authenticate: Basic Realm=»example » .

Notes

Bien qu’ils soient affichés ici sous forme de texte brut, le nom d’utilisateur et le mot de passe sont en fait encodés en base64.

 

Il existe deux types généraux de schémas d’authentification :

  • Schéma d’authentification de base, dans lequel le nom d’utilisateur et le mot de passe sont envoyés en texte clair au serveur.

    Le schéma d’authentification de base est basé sur le modèle selon lequel un client doit s’identifier avec un nom d’utilisateur et un mot de passe pour chaque domaine. Le serveur traite la demande uniquement si la demande est envoyée avec un en-tête d’autorisation qui inclut un nom d’utilisateur et un mot de passe valides.

  • Schémas de défi-réponse, tels que Kerberos, dans lesquels le serveur défie le client avec des données d’authentification. Le client transforme les données avec les informations d’identification de l’utilisateur et renvoie les données transformées au serveur pour l’authentification.

    Les schémas de défi-réponse permettent une authentification plus sécurisée. Dans un schéma de défi-réponse, le nom d’utilisateur et le mot de passe ne sont jamais transmis sur le réseau. Une fois que le client a sélectionné un schéma de défi-réponse, le serveur retourne un code de status approprié avec un défi qui contient les données d’authentification pour ce schéma. Le client renvoie ensuite la demande avec la réponse appropriée pour obtenir le service demandé. Les schémas de réponse aux défis peuvent prendre plusieurs échanges.

Le tableau suivant contient les schémas d’authentification pris en charge par WinHTTP, le type d’authentification et une description du schéma.

Schéma Type Description
De base (texte en clair) De base Utilise une chaîne encodée en base64 qui contient le nom d’utilisateur et le mot de passe.
Digest Défi-réponse Défis à l’aide d’une valeur nonce (chaîne de données spécifiée par le serveur). Une réponse valide contient une somme de contrôle du nom d’utilisateur, du mot de passe, de la valeur de nonce donnée, du verbe HTTP et de l’URI (Uniform Resource Identifier) demandé.
NTLM Défi-réponse Nécessite que les données d’authentification soient transformées avec les informations d’identification de l’utilisateur pour prouver l’identité. Pour que l’authentification NTLM fonctionne correctement, plusieurs échanges doivent avoir lieu sur la même connexion. Par conséquent, l’authentification NTLM ne peut pas être utilisée si un proxy intermédiaire ne prend pas en charge les connexions keep-alive. L’authentification NTLM échoue également si WinHttpSetOption est utilisé avec l’indicateur WINHTTP_DISABLE_KEEP_ALIVE qui désactive la sémantique keep-alive.
Passport Défi-réponse Utilise Microsoft Passport 1.4.
Negotiate Défi-réponse Si le serveur et le client utilisent Windows 2000 ou une version ultérieure, l’authentification Kerberos est utilisée. Sinon, l’authentification NTLM est utilisée. Kerberos est disponible dans Windows 2000 et les systèmes d’exploitation ultérieurs et est considéré comme plus sécurisé que l’authentification NTLM. Pour que l’authentification Negotiate fonctionne correctement, plusieurs échanges doivent avoir lieu sur la même connexion. Par conséquent, l’authentification Negotiate ne peut pas être utilisée si un proxy intermédiaire ne prend pas en charge les connexions keep-alive. L’authentification Negotiate échoue également si WinHttpSetOption est utilisé avec l’indicateur WINHTTP_DISABLE_KEEP_ALIVE qui désactive la sémantique keep-alive. Le schéma d’authentification Negotiate est parfois appelé Authentification Windows intégré.

 

Authentification dans les applications WinHTTP

L’interface de programmation d’application (API) WinHTTP fournit deux fonctions utilisées pour accéder aux ressources Internet dans les situations où l’authentification est requise : WinHttpSetCredentials et WinHttpQueryAuthSchemes.

Lorsqu’une réponse est reçue avec un code 401 ou 407 status, WinHttpQueryAuthSchemes peut être utilisé pour analyser les en-têtes d’authentification afin de déterminer les schémas d’authentification pris en charge et la cible d’authentification. La cible d’authentification est le serveur ou le proxy qui demande l’authentification. WinHttpQueryAuthSchemes détermine également le premier schéma d’authentification, à partir des schémas disponibles, en fonction des préférences de schéma d’authentification suggérées par le serveur. Cette méthode pour choisir un schéma d’authentification est le comportement suggéré par RFC 2616.

WinHttpSetCredentials permet à une application de spécifier le schéma d’authentification utilisé avec un nom d’utilisateur et un mot de passe valides pour une utilisation sur le serveur ou le proxy cible. Après avoir défini les informations d’identification et renvoyé la demande, les en-têtes nécessaires sont générés et ajoutés automatiquement à la demande. Étant donné que certains schémas d’authentification nécessitent plusieurs transactions , WinHttpSendRequest peut retourner l’erreur, ERROR_WINHTTP_RESEND_REQUEST. Lorsque cette erreur se produit, l’application doit continuer à renvoyer la demande jusqu’à ce qu’une réponse ne contenant pas de code status 401 ou 407 soit reçue. Un code 200 status indique que la ressource est disponible et que la demande a réussi. Consultez Codes d’état HTTP pour obtenir des codes status supplémentaires pouvant être retournés.

Si un schéma d’authentification et des informations d’identification acceptables sont connus avant l’envoi d’une demande au serveur, une application peut appeler WinHttpSetCredentials avant d’appeler WinHttpSendRequest. Dans ce cas, WinHTTP tente de pré-authentification auprès du serveur en fournissant des informations d’identification ou des données d’authentification dans la demande initiale au serveur. La pré-authentification peut réduire le nombre d’échanges dans le processus d’authentification et, par conséquent, améliorer les performances de l’application.

La pré-authentification peut être utilisée avec les schémas d’authentification suivants :

  • De base : toujours possible.
  • Négocier la résolution dans Kerberos - très probablement possible; la seule exception est lorsque les asymétries temporelles sont désactivées entre le client et le contrôleur de domaine.
  • (Négocier la résolution dans NTLM) - jamais possible.
  • NTLM : possible dans Windows Server 2008 R2 uniquement.
  • Digest : jamais possible.
  • Passeport - jamais possible; après le défi-réponse initial, WinHTTP utilise des cookies pour se pré-authentifier auprès de Passport.

Une application WinHTTP classique effectue les étapes suivantes pour gérer l’authentification.

Les informations d’identification définies par WinHttpSetCredentials ne sont utilisées que pour une seule requête. WinHTTP ne met pas en cache les informations d’identification à utiliser dans d’autres requêtes, ce qui signifie que les applications doivent être écrites et capables de répondre à plusieurs demandes. Si une connexion authentifiée est réutilisée, d’autres demandes peuvent ne pas être contestées, mais votre code doit pouvoir répondre à une demande à tout moment.

Exemple : Récupération d’un document

L’exemple de code suivant tente de récupérer un document spécifié à partir d’un serveur HTTP. Le code status est récupéré à partir de la réponse pour déterminer si l’authentification est requise. Si un code 200 status est trouvé, le document est disponible. Si un code status 401 ou 407 est trouvé, l’authentification est requise pour que le document puisse être récupéré. Pour tout autre code status, un message d’erreur s’affiche. Consultez Codes d’état HTTP pour obtenir la liste des codes status possibles.

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

Stratégie d’ouverture de session automatique

La stratégie d’ouverture de session automatique (ouverture de session automatique) détermine quand il est acceptable pour WinHTTP d’inclure les informations d’identification par défaut dans une demande. Les informations d’identification par défaut sont le jeton de thread actuel ou le jeton de session selon que WinHTTP est utilisé en mode synchrone ou asynchrone. Le jeton de thread est utilisé en mode synchrone, et le jeton de session est utilisé en mode asynchrone. Ces informations d’identification par défaut sont souvent le nom d’utilisateur et le mot de passe utilisés pour se connecter à Microsoft Windows.

La stratégie d’ouverture de session automatique a été implémentée pour empêcher que ces informations d’identification ne soient utilisées de manière occasionnelle pour s’authentifier auprès d’un serveur non approuvé. Par défaut, le niveau de sécurité est défini sur WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, ce qui permet d’utiliser les informations d’identification par défaut uniquement pour les requêtes Intranet. La stratégie d’ouverture de session automatique s’applique uniquement aux schémas d’authentification NTLM et Negotiate. Les informations d’identification ne sont jamais transmises automatiquement avec d’autres schémas.

La stratégie d’ouverture de session automatique peut être définie à l’aide de la fonction WinHttpSetOption avec l’indicateur WINHTTP_OPTION_AUTOLOGON_POLICY . Cet indicateur s’applique uniquement au handle de requête. Lorsque la stratégie est définie sur WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW, les informations d’identification par défaut peuvent être envoyées à tous les serveurs. Lorsque la stratégie est définie sur WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH, les informations d’identification par défaut ne peuvent pas être utilisées pour l’authentification. Il est vivement recommandé d’utiliser l’ouverture de session automatique au niveau MEDIUM.

Noms d’utilisateur et mots de passe stockés

Windows XP a introduit le concept de noms d’utilisateur et de mots de passe stockés. Si les informations d’identification Passport d’un utilisateur sont enregistrées via l’Assistant Inscription de passeport ou la boîte de dialogue d’informations d’identification standard, elles sont enregistrées dans les noms d’utilisateur et mots de passe stockés. Lors de l’utilisation de WinHTTP sur Windows XP ou version ultérieure, WinHTTP utilise automatiquement les informations d’identification dans les noms d’utilisateur et mots de passe stockés si les informations d’identification ne sont pas définies explicitement. Cela est similaire à la prise en charge des informations d’identification d’ouverture de session par défaut pour NTLM/Kerberos. Toutefois, l’utilisation des informations d’identification Passport par défaut n’est pas soumise aux paramètres de stratégie d’ouverture de session automatique.