Autenticação no WinHTTP

Alguns servidores HTTP e proxies exigem autenticação antes de permitir o acesso aos recursos na Internet. As funções do WinHTTP (Serviços HTTP do Microsoft Windows) dão suporte à autenticação de servidor e proxy para sessões HTTP.

Sobre a autenticação HTTP

Se a autenticação for necessária, o aplicativo HTTP receberá um código status de 401 (o servidor requer autenticação) ou 407 (o proxy requer autenticação). Junto com o código status, o proxy ou servidor envia um ou mais cabeçalhos de autenticação: WWW-Authenticate (para autenticação de servidor) ou Proxy-Authenticate (para autenticação de proxy).

Cada cabeçalho de autenticação contém um esquema de autenticação com suporte e, para os esquemas Básico e Digest, um realm. Se houver suporte para vários esquemas de autenticação, o servidor retornará vários cabeçalhos de autenticação. O valor realm diferencia maiúsculas de minúsculas e define um conjunto de servidores ou proxies para os quais as mesmas credenciais são aceitas. Por exemplo, o cabeçalho "WWW-Authenticate: Basic Realm="example"" pode ser retornado quando a autenticação do servidor é necessária. Esse cabeçalho especifica que as credenciais do usuário devem ser fornecidas para o domínio "exemplo".

Um aplicativo HTTP pode incluir um campo de cabeçalho de autorização com uma solicitação enviada ao servidor. O cabeçalho de autorização contém o esquema de autenticação e a resposta apropriada exigida por esse esquema. Por exemplo, o cabeçalho "Authorization: Basic <username:password>" seria adicionado à solicitação e enviado ao servidor se o cliente recebesse o cabeçalho de resposta "WWW-Authenticate: Basic Realm="example"".

Observação

Embora eles sejam mostrados aqui como texto sem formatação, o nome de usuário e a senha são, na verdade, codificados em base64.

 

Há dois tipos gerais de esquemas de autenticação:

  • Esquema de autenticação básico, no qual o nome de usuário e a senha são enviados em texto claro para o servidor.

    O esquema de autenticação básica baseia-se no modelo que um cliente deve identificar-se com um nome de usuário e uma senha para cada realm. O servidor atende a solicitação somente se a solicitação for enviada com um cabeçalho de autorização que inclui um nome de usuário e uma senha válidos.

  • Esquemas de resposta a desafios, como Kerberos, nos quais o servidor desafia o cliente com dados de autenticação. O cliente transforma os dados com as credenciais do usuário e envia os dados transformados de volta para o servidor para autenticação.

    Os esquemas de resposta a desafios permitem uma autenticação mais segura. Em um esquema de desafio-resposta, o nome de usuário e a senha nunca são transmitidos pela rede. Depois que o cliente seleciona um esquema de desafio-resposta, o servidor retorna um código status apropriado com um desafio que contém os dados de autenticação desse esquema. Em seguida, o cliente reenvia a solicitação com a resposta adequada para obter o serviço solicitado. Os esquemas de resposta a desafios podem levar várias trocas para serem concluídos.

A tabela a seguir contém os esquemas de autenticação compatíveis com o WinHTTP, o tipo de autenticação e uma descrição do esquema.

Esquema Tipo Descrição
Básico (texto sem formatação) Basic Usa uma cadeia de caracteres codificada em base64 que contém o nome de usuário e a senha.
Digest Desafio-resposta Desafios ao usar um valor nonce (uma cadeia de dados especificada pelo servidor). Uma resposta válida contém uma soma de verificação do nome de usuário, da senha, do valor de nonce fornecido, do verbo HTTP e do URI (Uniform Resource Identifier) solicitado.
NTLM Desafio-resposta Exige que os dados de autenticação sejam transformados com as credenciais do usuário para provar a identidade. Para que a autenticação NTLM funcione corretamente, várias trocas devem ocorrer na mesma conexão. Portanto, a autenticação NTLM não poderá ser usada se um proxy intermediário não oferecer suporte a conexões keep alive. A autenticação NTLM também falhará se WinHttpSetOption for usado com o sinalizador WINHTTP_DISABLE_KEEP_ALIVE que desabilita a semântica keep alive.
Passport Desafio-resposta Usa o Microsoft Passport 1.4.
Negotiate Desafio-resposta Se o servidor e o cliente estiverem usando o Windows 2000 ou posterior, a autenticação Kerberos será usada. Caso contrário, a autenticação NTLM será usada. O Kerberos está disponível no Windows 2000 e em sistemas operacionais posteriores e é considerado mais seguro do que a autenticação NTLM. Para que a autenticação negotiate funcione corretamente, várias trocas devem ocorrer na mesma conexão. Portanto, a autenticação Negotiate não poderá ser usada se um proxy intermediário não oferecer suporte a conexões keep alive. A autenticação de negociação também falhará se WinHttpSetOption for usado com o sinalizador WINHTTP_DISABLE_KEEP_ALIVE que desabilita a semântica keep alive. Às vezes, o esquema de autenticação Negotiate é chamado de autenticação do Windows Integrado.

 

Autenticação em aplicativos WinHTTP

A API (interface de programação de aplicativo) WinHTTP fornece duas funções usadas para acessar recursos da Internet em situações em que a autenticação é necessária: WinHttpSetCredentials e WinHttpQueryAuthSchemes.

Quando uma resposta é recebida com um código 401 ou 407 status, WinHttpQueryAuthSchemes pode ser usado para analisar os cabeçalhos de autenticação para determinar os esquemas de autenticação com suporte e o destino de autenticação. O destino de autenticação é o servidor ou proxy que solicita a autenticação. WinHttpQueryAuthSchemes também determina o primeiro esquema de autenticação, dos esquemas disponíveis, com base nas preferências de esquema de autenticação sugeridas pelo servidor. Esse método para escolher um esquema de autenticação é o comportamento sugerido pelo RFC 2616.

WinHttpSetCredentials permite que um aplicativo especifique o esquema de autenticação usado junto com um nome de usuário e senha válidos para uso no servidor ou proxy de destino. Depois de definir as credenciais e reenviar a solicitação, os cabeçalhos necessários são gerados e adicionados à solicitação automaticamente. Como alguns esquemas de autenticação exigem várias transações , WinHttpSendRequest pode retornar o erro, ERROR_WINHTTP_RESEND_REQUEST. Quando esse erro é encontrado, o aplicativo deve continuar a reenviar a solicitação até que uma resposta seja recebida que não contenha um código 401 ou 407 status. Um código de status 200 indica que o recurso está disponível e a solicitação foi bem-sucedida. Consulte Códigos de status HTTP para obter códigos de status adicionais que podem ser retornados.

Se um esquema de autenticação e credenciais aceitáveis forem conhecidos antes de uma solicitação ser enviada ao servidor, um aplicativo poderá chamar WinHttpSetCredentials antes de chamar WinHttpSendRequest. Nesse caso, o WinHTTP tenta pré-autenticação com o servidor fornecendo credenciais ou dados de autenticação na solicitação inicial para o servidor. A pré-autenticação pode diminuir o número de trocas no processo de autenticação e, portanto, melhorar o desempenho do aplicativo.

A pré-autenticação pode ser usada com os seguintes esquemas de autenticação:

  • Básico - sempre possível.
  • Negociar a resolução no Kerberos – muito provavelmente possível; a única exceção é quando as distorções de tempo estão desativadas entre o cliente e o controlador de domínio.
  • (Negociar a resolução em NTLM) – nunca é possível.
  • NTLM – possível somente no Windows Server 2008 R2.
  • Resumo – nunca é possível.
  • Passaporte - nunca possível; após o desafio-resposta inicial, o WinHTTP usa cookies para pré-autenticar no Passport.

Um aplicativo WinHTTP típico conclui as etapas a seguir para lidar com a autenticação.

As credenciais definidas por WinHttpSetCredentials são usadas apenas para uma solicitação. O WinHTTP não armazena em cache as credenciais a serem usadas em outras solicitações, o que significa que os aplicativos devem ser gravados que possam responder a várias solicitações. Se uma conexão autenticada for reutilizada, outras solicitações podem não ser contestadas, mas seu código deve ser capaz de responder a uma solicitação a qualquer momento.

Exemplo: recuperando um documento

O código de exemplo a seguir tenta recuperar um documento especificado de um servidor HTTP. O código status é recuperado da resposta para determinar se a autenticação é necessária. Se um código de status 200 for encontrado, o documento estará disponível. Se um código de status de 401 ou 407 for encontrado, a autenticação será necessária para que o documento possa ser recuperado. Para qualquer outro código status, uma mensagem de erro é exibida. Consulte Códigos de status HTTP para obter uma lista de possíveis códigos de status.

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

Política de Logon Automático

A política de logon automático (logon automático) determina quando é aceitável que o WinHTTP inclua as credenciais padrão em uma solicitação. As credenciais padrão são o token de thread atual ou o token de sessão, dependendo se WinHTTP é usado no modo síncrono ou assíncrono. O token de thread é usado no modo síncrono e o token de sessão é usado no modo assíncrono. Essas credenciais padrão geralmente são o nome de usuário e a senha usados para fazer logon no Microsoft Windows.

A política de logon automático foi implementada para impedir que essas credenciais sejam usadas casualmente para autenticar em um servidor não confiável. Por padrão, o nível de segurança é definido como WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, o que permite que as credenciais padrão sejam usadas apenas para solicitações da Intranet. A política de logon automático só se aplica aos esquemas de autenticação NTLM e Negotiate. As credenciais nunca são transmitidas automaticamente com outros esquemas.

A política de logon automático pode ser definida usando a função WinHttpSetOption com o sinalizador WINHTTP_OPTION_AUTOLOGON_POLICY . Esse sinalizador se aplica somente ao identificador de solicitação. Quando a política é definida como WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW, as credenciais padrão podem ser enviadas para todos os servidores. Quando a política é definida como WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH, as credenciais padrão não podem ser usadas para autenticação. É altamente recomendável que você use o logon automático no nível MEDIUM.

Nomes de usuário e senhas armazenados

O Windows XP introduziu o conceito de Nomes de Usuário Armazenados e Senhas. Se as credenciais do Passport de um usuário forem salvas por meio do Assistente de Registro de Passaporte ou da caixa de diálogo de credencial padrão, elas serão salvas nos Nomes de Usuário Armazenados e Senhas. Ao usar WinHTTP no Windows XP ou posterior, o WinHTTP usará automaticamente as credenciais nos Nomes de Usuário Armazenados e Senhas se as credenciais não forem definidas explicitamente. Isso é semelhante ao suporte de credenciais de logon padrão para NTLM/Kerberos. No entanto, o uso de credenciais padrão do Passport não está sujeito às configurações de política de logon automático.