Проверка подлинности в WinHTTP

Некоторые HTTP-серверы и прокси-серверы требуют проверки подлинности, прежде чем разрешать доступ к ресурсам в Интернете. Функции служб HTTP Microsoft Windows (WinHTTP) поддерживают проверку подлинности сервера и прокси-сервера для сеансов HTTP.

Сведения о проверке подлинности HTTP

Если требуется проверка подлинности, http-приложение получает код состояния 401 (сервер требует проверки подлинности) или 407 (прокси-сервер требует проверки подлинности). Наряду с кодом состояния прокси-сервер или сервер отправляет один или несколько заголовков проверки подлинности: WWW-Authenticate (для проверки подлинности сервера) или Proxy-Authenticate (для проверки подлинности прокси-сервера).

Каждый заголовок проверки подлинности содержит поддерживаемую схему проверки подлинности, а для схем Basic и Digest — область. Если поддерживается несколько схем проверки подлинности, сервер возвращает несколько заголовков проверки подлинности. Значение области учитывает регистр и определяет набор серверов или прокси-серверов, для которых принимаются одни и те же учетные данные. Например, если требуется проверка подлинности сервера, может быть возвращен заголовок WWW-Authentication: Basic Realm="example". Этот заголовок указывает, что для домена example необходимо указать учетные данные пользователя.

HTTP-приложение может содержать поле заголовка авторизации с запросом, отправляемым на сервер. Заголовок авторизации содержит схему проверки подлинности и соответствующий ответ, необходимый для этой схемы. Например, заголовок "Authorization: Basic <username:password>" будет добавлен в запрос и отправлен на сервер, если клиент получил заголовок ответа "WWW-Authenticate: Basic Realm="example"".

Примечание

Хотя они показаны здесь в виде обычного текста, имя пользователя и пароль фактически кодируются в кодировке Base64.

 

Существует два основных типа схем проверки подлинности:

  • Обычная схема проверки подлинности, в которой имя пользователя и пароль отправляются на сервер в виде простого текста.

    Базовая схема проверки подлинности основана на модели, которую клиент должен идентифицировать с помощью имени пользователя и пароля для каждой области. Сервер обслуживает запрос только в том случае, если запрос отправляется с заголовком авторизации, который содержит допустимое имя пользователя и пароль.

  • Схемы ответа на запросы, такие как Kerberos, в которых сервер вызывает у клиента данные проверки подлинности. Клиент преобразует данные с учетными данными пользователя и отправляет преобразованные данные обратно на сервер для проверки подлинности.

    Схемы ответа на запросы обеспечивают более безопасную проверку подлинности. В схеме ответа на запрос имя пользователя и пароль никогда не передаются по сети. После того как клиент выберет схему "запрос—ответ", сервер возвращает соответствующий код состояния с запросом, содержащим данные проверки подлинности для этой схемы. Затем клиент повторно отправляет запрос с соответствующим ответом, чтобы получить запрошенную службу. Для выполнения схем ответа на запросы может потребоваться несколько обменов.

В следующей таблице приведены схемы проверки подлинности, поддерживаемые WinHTTP, тип проверки подлинности и описание схемы.

Схема Тип Описание
Базовый (открытый текст) Basic Использует строку в кодировке Base64 , содержащую имя пользователя и пароль.
Digest (дайджест) Ответ на запрос Проблемы с использованием значения nonce (указанной сервером строки данных). Допустимый ответ содержит контрольную сумму имени пользователя, пароля, заданного значения nonce, HTTP-команды и запрошенного универсального идентификатора ресурса (URI).
NTLM Ответ на запрос Требует, чтобы данные проверки подлинности преобразовылись с учетными данными пользователя для подтверждения удостоверения. Для правильной работы проверки подлинности NTLM при одном подключении должно выполняться несколько обменов. Таким образом, проверку подлинности NTLM нельзя использовать, если промежуточный прокси-сервер не поддерживает поддерживающие подключения. Проверка подлинности NTLM также завершается ошибкой, если используется WinHttpSetOption с флагом WINHTTP_DISABLE_KEEP_ALIVE , который отключает семантику поддержания активности.
Паспорт Ответ на запрос Использует Microsoft Passport 1.4.
Согласование Ответ на запрос Если сервер и клиент используют Windows 2000 или более поздней версии, используется проверка подлинности Kerberos. В противном случае используется проверка подлинности NTLM. Протокол Kerberos доступен в операционных системах Windows 2000 и более поздних версий и считается более безопасным, чем проверка подлинности NTLM. Чтобы проверка подлинности Negotiate правильно функционировала, при одном подключении должно выполняться несколько обменов. Поэтому проверку подлинности Negotiate нельзя использовать, если промежуточный прокси-сервер не поддерживает поддерживающие подключения. Согласование проверки подлинности также завершается ошибкой, если используется WinHttpSetOption с флагом WINHTTP_DISABLE_KEEP_ALIVE , который отключает семантику поддержания активности. Схема проверки подлинности Negotiate иногда называется интегрированной проверка подлинности Windows.

 

Проверка подлинности в приложениях WinHTTP

Интерфейс API winHTTP предоставляет две функции, используемые для доступа к интернет-ресурсам в ситуациях, когда требуется проверка подлинности: WinHttpSetCredentials и WinHttpQueryAuthSchemes.

При получении ответа с кодом состояния 401 или 407 можно использовать WinHttpQueryAuthSchemes для анализа заголовков проверки подлинности для определения поддерживаемых схем проверки подлинности и целевого объекта проверки подлинности. Целевым объектом проверки подлинности является сервер или прокси-сервер, запрашивающий проверку подлинности. WinHttpQueryAuthSchemes также определяет первую схему проверки подлинности на основе доступных схем на основе предпочтений схемы проверки подлинности, предлагаемых сервером. Этот метод выбора схемы проверки подлинности является поведением, предлагаемым RFC 2616.

WinHttpSetCredentials позволяет приложению указать схему проверки подлинности, используемую вместе с допустимым именем пользователя и паролем для использования на целевом сервере или прокси-сервере. После настройки учетных данных и повторной отправки запроса необходимые заголовки создаются и добавляются в запрос автоматически. Так как для некоторых схем проверки подлинности требуется несколько транзакций , WinHttpSendRequest может вернуть ошибку, ERROR_WINHTTP_RESEND_REQUEST. При обнаружении этой ошибки приложение должно продолжать повторно отправлять запрос до получения ответа, не содержащего код состояния 401 или 407. Код состояния 200 указывает, что ресурс доступен и запрос выполнен успешно. Дополнительные коды состояния, которые можно вернуть, см. в разделе Коды состояния HTTP .

Если приемлемая схема проверки подлинности и учетные данные известны перед отправкой запроса на сервер, приложение может вызвать WinHttpSetCredentials перед вызовом WinHttpSendRequest. В этом случае WinHTTP пытается выполнить предварительную проверку подлинности на сервере, предоставляя учетные данные или данные проверки подлинности в исходном запросе к серверу. Предварительная проверка подлинности может уменьшить количество обменов в процессе проверки подлинности и, следовательно, повысить производительность приложения.

Предварительную проверку подлинности можно использовать со следующими схемами проверки подлинности:

  • Базовый — всегда возможно.
  • Согласование разрешения в Kerberos — весьма вероятно, возможно; Единственным исключением является то, что между клиентом и контроллером домена отключены временные различия.
  • (Согласование разрешения в NTLM) — никогда не возможно.
  • NTLM — возможно только в Windows Server 2008 R2.
  • Дайджест - никогда не возможно.
  • Паспорт — никогда не возможно; После первоначального ответа на запрос WinHTTP использует файлы cookie для предварительной проверки подлинности в Passport.

Для обработки проверки подлинности обычное приложение WinHTTP выполняет следующие действия.

  • Запросите ресурс с помощью WinHttpOpenRequest и WinHttpSendRequest.
  • Проверьте заголовки ответа с помощью WinHttpQueryHeaders.
  • Если возвращается код состояния 401 или 407, указывающий на необходимость проверки подлинности, вызовите WinHttpQueryAuthSchemes , чтобы найти приемлемую схему.
  • Задайте схему проверки подлинности, имя пользователя и пароль с помощью WinHttpSetCredentials.
  • Повторно отправьте запрос с тем же дескриптором запроса, вызвав WinHttpSendRequest.

Учетные данные, заданные WinHttpSetCredentials , используются только для одного запроса. WinHTTP не кэширует учетные данные для использования в других запросах. Это означает, что необходимо написать приложения, способные отвечать на несколько запросов. Если подключение с проверкой подлинности используется повторно, другие запросы могут не быть оспорены, но ваш код должен иметь возможность ответить на запрос в любое время.

Пример. Получение документа

Следующий пример кода пытается получить указанный документ с HTTP-сервера. Код состояния извлекается из ответа, чтобы определить, требуется ли проверка подлинности. Если найден код состояния 200, документ доступен. Если найден код состояния 401 или 407, перед извлечением документа требуется проверка подлинности. Для любого другого кода состояния отображается сообщение об ошибке. Список возможных кодов состояния см. в разделе Коды состояния 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 );
}

Политика автоматического входа

Политика автоматического входа определяет, когда winHTTP может включать учетные данные по умолчанию в запрос. Учетные данные по умолчанию — это маркер текущего потока или маркер сеанса в зависимости от того, используется ли WinHTTP в синхронном или асинхронном режиме. Маркер потока используется в синхронном режиме, а маркер сеанса — в асинхронном режиме. Эти учетные данные по умолчанию часто являются именем пользователя и паролем, используемыми для входа в Microsoft Windows.

Политика автоматического входа была реализована, чтобы предотвратить случайное использование этих учетных данных для проверки подлинности на ненадежном сервере. По умолчанию для уровня безопасности задано значение WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, что позволяет использовать учетные данные по умолчанию только для запросов интрасети. Политика автоматического входа применяется только к схемам проверки подлинности NTLM и Negotiate. Учетные данные никогда не передаются автоматически с другими схемами.

Политику автоматического входа можно задать с помощью функции WinHttpSetOption с флагом WINHTTP_OPTION_AUTOLOGON_POLICY . Этот флаг применяется только к дескриптором запроса. Если для политики задано значение WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW, учетные данные по умолчанию можно отправлять на все серверы. Если для политики задано значение WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH, учетные данные по умолчанию нельзя использовать для проверки подлинности. Настоятельно рекомендуется использовать автоматический вход на уровне MEDIUM.

Сохранение имен пользователей и паролей

В Windows XP представлена концепция хранимых имен пользователей и паролей. Если учетные данные пользователя passport сохраняются с помощью мастера регистрации паспортов или стандартного диалогового окна учетных данных, они сохраняются в сохраненных именах пользователей и паролях. При использовании WinHTTP в Windows XP или более поздних версиях WinHTTP автоматически использует учетные данные в разделе Сохраненные имена пользователей и пароли, если учетные данные не заданы явным образом. Это похоже на поддержку учетных данных входа по умолчанию для NTLM/Kerberos. Однако использование учетных данных Passport по умолчанию не зависит от параметров политики автоматического входа.