WinHTTP での認証

一部の HTTP サーバーとプロキシでは、インターネット上のリソースへのアクセスを許可する前に認証が必要です。 Microsoft Windows HTTP Services (WinHTTP) 関数は、HTTP セッションのサーバー認証とプロキシ認証をサポートします。

HTTP 認証について

認証が必要な場合、HTTP アプリケーションは状態コード 401 (サーバーで認証が必要) または 407 (プロキシで認証が必要) を受け取ります。 状態コードと共に、プロキシまたはサーバーは 1 つ以上の認証ヘッダーを送信します。WWW-Authenticate (サーバー認証の場合) またはProxy-Authenticate (プロキシ認証の場合)。

各認証ヘッダーには、サポートされている認証スキームと、Basic および Digest スキームの領域が含まれています。 複数の認証スキームがサポートされている場合、サーバーは複数の認証ヘッダーを返します。 領域の値では大文字と小文字が区別され、同じ資格情報を受け入れるサーバーまたはプロキシのセットを定義します。 たとえば、サーバー認証が必要な場合は、ヘッダー "WWW-Authenticate: Basic Realm="example"" が返される場合があります。 このヘッダーは、"example" ドメインに対してユーザー資格情報を指定する必要があることを指定します。

HTTP アプリケーションには、サーバーに送信する要求を含む承認ヘッダー フィールドを含めることができます。 承認ヘッダーには、認証スキームとそのスキームに必要な適切な応答が含まれています。 たとえば、"Authorization: Basic <username:password>" というヘッダーが要求に追加され、クライアントが応答ヘッダー "WWW-Authenticate: Basic Realm="example" を受信した場合にサーバーに送信されます。

注意

ここではプレーン テキストとして表示されますが、ユーザー名とパスワードは実際には base64 でエンコードされます

 

認証スキームには、次の 2 つの一般的な種類があります。

  • 基本認証スキーム。ユーザー名とパスワードがクリア テキストでサーバーに送信されます。

    基本認証スキームは、クライアントが各領域のユーザー名とパスワードを使用して自身を識別する必要があるモデルに基づいています。 サーバーは、有効なユーザー名とパスワードを含む承認ヘッダーを使用して要求が送信された場合にのみ要求を処理します。

  • Kerberos などのチャレンジ応答スキーム。サーバーが 認証データを使用してクライアントにチャレンジします。 クライアントは、ユーザー資格情報を使用してデータを変換し、変換されたデータを認証のためにサーバーに送信します。

    チャレンジ応答スキームを使用すると、より安全な認証が可能になります。 チャレンジ応答スキームでは、ユーザー名とパスワードがネットワーク経由で送信されることはありません。 クライアントがチャレンジ応答スキームを選択すると、サーバーは、そのスキームの 認証データ を含むチャレンジを含む適切な状態コードを返します。 その後、クライアントは要求されたサービスを取得するための適切な応答を使用して要求を再送信します。 チャレンジ応答スキームでは、完了するために複数の交換が必要な場合があります。

次の表に、WinHTTP でサポートされている認証スキーム、認証の種類、およびスキームの説明を示します。

Scheme Type 説明
Basic (プレーンテキスト) Basic ユーザー名とパスワードを含む base64 でエンコードされた 文字列を使用します。
ダイジェスト チャレンジ応答 nonce (サーバー指定のデータ文字列) 値を使用する場合の課題。 有効な応答には、ユーザー名、パスワード、指定された nonce 値、 HTTP 動詞、および要求された Uniform Resource Identifier (URI) のチェックサムが含まれます。
NTLM チャレンジ応答 ID を証明するには、ユーザー資格情報を使用して 認証データ を変換する必要があります。 NTLM 認証が正しく機能するには、同じ接続で複数の交換を行う必要があります。 そのため、介在するプロキシがキープアライブ接続をサポートしていない場合は、NTLM 認証を使用できません。 また、キープアライブ セマンティクスを無効にするWINHTTP_DISABLE_KEEP_ALIVE フラグと共WinHttpSetOption を使用すると、NTLM 認証も失敗します。
Passport チャレンジ応答 Microsoft Passport 1.4 を使用します。
ネゴシエート チャレンジ応答 サーバーとクライアントの両方が Windows 2000 以降を使用している場合は、Kerberos 認証が使用されます。 それ以外の場合は、NTLM 認証が使用されます。 Kerberos は Windows 2000 以降のオペレーティング システムで使用でき、NTLM 認証よりもセキュリティが高いと見なされます。 ネゴシエート認証が正しく機能するためには、同じ接続で複数の交換を行う必要があります。 そのため、介在するプロキシがキープアライブ接続をサポートしていない場合は、ネゴシエート認証を使用できません。 また、キープアライブ セマンティクスを無効にするWINHTTP_DISABLE_KEEP_ALIVE フラグと共に WinHttpSetOption を使用すると、ネゴシエート認証も失敗します。 ネゴシエート認証スキームは、統合Windows 認証と呼ばれることもあります。

 

WinHTTP アプリケーションでの認証

WinHTTP アプリケーション プログラミング インターフェイス (API) には、認証が必要な状況でインターネット リソースにアクセスするために使用される 2 つの関数 ( WinHttpSetCredentialsWinHttpQueryAuthSchemes) が用意されています。

401 または 407 状態コードで応答を受信すると、 WinHttpQueryAuthSchemes を使用して認証ヘッダーを解析し、サポートされている認証スキームと認証ターゲットを決定できます。 認証ターゲットは、認証を要求するサーバーまたはプロキシです。 また、WinHttpQueryAuthSchemes は、サーバーによって提案された認証スキームの基本設定に基づいて、使用可能なスキームから最初の認証スキームを決定します。 認証スキームを選択するためのこの方法は、 RFC 2616 で推奨される動作です。

WinHttpSetCredentials を使用すると、アプリケーションは、ターゲット サーバーまたはプロキシで使用する有効なユーザー名とパスワードと共に使用される認証スキームを指定できます。 資格情報を設定して要求を再送信すると、必要なヘッダーが生成され、要求に自動的に追加されます。 一部の認証スキームでは複数のトランザクションが必要であるため 、WinHttpSendRequest はエラーを返す可能性があります。ERROR_WINHTTP_RESEND_REQUEST。 このエラーが発生した場合、401 または 407 状態コードを含まない応答が受信されるまで、アプリケーションは要求を再送信し続ける必要があります。 200 状態コードは、リソースが使用可能であり、要求が成功したことを示します。 返される可能性があるその他の状態コードについては、「 HTTP 状態コード」を参照してください。

要求がサーバーに送信される前に許容される認証スキームと資格情報がわかっている場合、アプリケーションは WinHttpSendRequest を呼び出す前に WinHttpSetCredentials を呼び出すことができます。 この場合、WinHTTP は、サーバーへの最初の要求で資格情報または 認証データ を指定することで、サーバーとの事前認証を試みます。 事前認証により、認証プロセスでの交換の数が減り、アプリケーションのパフォーマンスが向上する可能性があります。

事前認証は、次の認証スキームで使用できます。

  • Basic - 常に可能です。
  • Kerberos への解決をネゴシエートする - 可能性が非常に高い。唯一の例外は、クライアントとドメイン コントローラーの間で時間スキューがオフになっている場合です。
  • (NTLM への解決をネゴシエートする) - 決して不可能です。
  • NTLM - Windows Server 2008 R2 でのみ可能です。
  • ダイジェスト - 決して不可能です。
  • Passport - 決して不可能です。最初のチャレンジ応答の後、WinHTTP は Cookie を使用して Passport に対する事前認証を行います。

一般的な WinHTTP アプリケーションでは、認証を処理するために次の手順を実行します。

WinHttpSetCredentials によって設定された資格情報は、1 つの要求にのみ使用されます。 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 およびネゴシエート認証スキームにのみ適用されます。 資格情報は、他のスキームと共に自動的に送信されることはありません。

自動ログオン ポリシーは、 winHttpSetOption 関数と WINHTTP_OPTION_AUTOLOGON_POLICY フラグを使用して設定できます。 このフラグは、要求ハンドルにのみ適用されます。 ポリシーが WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW に設定されている場合、既定の資格情報をすべてのサーバーに送信できます。 ポリシーが WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH に設定されている場合、既定の資格情報を認証に使用することはできません。 MEDIUM レベルで自動ログオンを使用することを強くお勧めします。

格納されたユーザー名およびパスワード

Windows XP では、保存されているユーザー名とパスワードの概念が導入されました。 ユーザーの Passport 資格情報が Passport 登録ウィザード または標準の 資格情報ダイアログで保存されている場合は、保存されたユーザー名とパスワードに保存されます。 Windows XP 以降で WinHTTP を使用する場合、資格情報が明示的に設定されていない場合、WinHTTP は保存されたユーザー名とパスワードの資格情報を自動的に使用します。 これは、NTLM/Kerberos の既定のログオン資格情報のサポートに似ています。 ただし、既定の Passport 資格情報の使用は、自動ログオン ポリシー設定の対象になりません。