Partilhar via


Autenticação no WinHTTP

Alguns servidores HTTP e proxies requerem autenticação antes de permitir o acesso a recursos na Internet. As funções do Microsoft Windows HTTP Services (WinHTTP) suportam 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 de status de 401 (o servidor requer autenticação) ou 407 (o proxy requer autenticação). Junto com o código de status, o proxy ou servidor envia um ou mais cabeçalhos autenticados: WWW-Authenticate (para autenticação de servidor) ou Proxy-Authenticate (para autenticação de proxy).

Cada cabeçalho autenticado contém um esquema de autenticação suportado e, para os esquemas Basic 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 de 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. Este 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 que envia 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 simples, o nome de usuário e a senha são, na verdade, codificados em base64.

 

Existem dois tipos gerais de esquemas de autenticação:

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

    O esquema de autenticação Básica é baseado no modelo de que um cliente deve se identificar com um nome de usuário e 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 inclua um nome de usuário e senha válidos.

  • Esquemas de desafio-resposta, como Kerberos, em que 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 ao servidor para autenticação.

    Os esquemas de desafio-resposta permitem uma autenticação mais segura. Num esquema de desafio-resposta, o nome de utilizador e a palavra-passe nunca são transmitidos através da rede. Depois que o cliente seleciona um esquema de desafio-resposta, o servidor retorna um código de status apropriado com um desafio que contém os dados de autenticação para esse esquema. Em seguida, o cliente reenvia o pedido com a resposta adequada para obter o serviço solicitado. Os esquemas de desafio-resposta podem levar vários intercâmbios para serem concluídos.

A tabela a seguir contém os esquemas de autenticação suportados pelo WinHTTP, o tipo de autenticação e uma descrição do esquema.

Regime Tipo Descrição
Básico (texto simples) Básico Usa uma cadeia de caracteres decodificadabase64 que contém o nome de usuário e a senha.
Resumo Desafio-resposta Desafios usando 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 nonce fornecido, do verbo HTTP e do URI (Uniform Resource Identifier) solicitado.
NTLM Desafio-resposta Requer 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 pode ser usada se um proxy interveniente não oferecer suporte a conexões keep-alive. A autenticação NTLM também falhará se WinHttpSetOption for usada com o sinalizador WINHTTP_DISABLE_KEEP_ALIVE que desabilita a semântica keep-alive.
Passaporte Desafio-resposta Usa Microsoft Passport 1.4.
Negociar 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. Kerberos está disponível no Windows 2000 e 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 pode ser usada se um proxy interveniente não oferecer suporte a conexões keep-alive. A autenticação de negociação também falhará se WinHttpSetOption for usada com o sinalizador de WINHTTP_DISABLE_KEEP_ALIVE que desabilita a semântica keep-alive. O esquema de autenticação Negociar às vezes é chamado de autenticação integrada do Windows.

 

Autenticação em aplicativos WinHTTP

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

Quando uma resposta é recebida com um código de status 401 ou 407, WinHttpQueryAuthSchemes pode ser usada para analisar os cabeçalhos de autenticação para determinar os esquemas de autenticação suportados e o destino de autenticação. O destino de autenticação é o servidor ou proxy que solicita autenticação. WinHttpQueryAuthSchemes também determina o primeiro esquema de autenticação, a partir dos esquemas disponíveis, com base nas preferências do esquema de autenticação sugeridas pelo servidor. Este 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 de status 401 ou 407. Um código de status 200 indica que o recurso está disponível e a solicitação foi bem-sucedida. Consulte de códigos de status HTTP para obter códigos de status adicionais que podem ser retornados.

Se um esquema de autenticação aceitável e credenciais forem conhecidos antes de uma solicitação ser enviada ao servidor, um aplicativo pode chamar WinHttpSetCredentials antes de chamar WinHttpSendRequest. Nesse caso, o WinHTTP tenta a 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 em 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 resolvendo em NTLM) - nunca é possível.
  • NTLM - possível apenas no Windows Server 2008 R2.
  • Digest - 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 manipular a autenticação.

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

Exemplo: Recuperar um documento

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

#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 diretiva 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 o 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 evitar que essas credenciais sejam usadas casualmente para autenticação 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 de 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 diretiva 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 MÉDIO.

Nomes de usuário e senhas armazenados

O Windows XP introduziu o conceito de Nomes de Usuário e Senhas Armazenados. Se as credenciais do Passport de um usuário forem salvas por meio da do Assistente de Registro do Passport ou da caixa de diálogo padrão Credential, elas serão salvas em Nomes de usuário e senhas armazenados. Ao usar o WinHTTP no Windows XP ou posterior, o WinHTTP usa automaticamente as credenciais nos Nomes de Usuário e Senhas Armazenados se as credenciais não estiverem 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 diretiva de logon automático.