處理驗證

某些 Proxy 和伺服器需要驗證,才能授與網際網路上資源的存取權。 WinINet 函式支援 HTTP 會話的伺服器和 Proxy 驗證。 Ftp 伺服器的驗證必須由 InternetConnect 函式處理。 目前不支援 FTP 閘道驗證。

關於 HTTP 驗證

如果需要驗證,如果伺服器需要驗證,用戶端應用程式會收到狀態碼 401;如果 Proxy 需要驗證,則為 407。 使用狀態碼時,Proxy 或伺服器會傳送一或多個驗證回應標頭—Proxy 驗證 (用於 Proxy 驗證) 或伺服器驗證WWW-Authenticate () 。

每個驗證回應標頭都包含可用的驗證配置和領域。 如果支援多個驗證配置,伺服器會傳回多個驗證回應標頭。 領域值會區分大小寫,並定義 Proxy 或伺服器上的保護空間。 例如,標頭 「WWW-Authentication: Basic Realm=」example「」 是需要伺服器驗證時傳回的標頭範例。

傳送要求的用戶端應用程式可以藉由包含具有要求的 [授權標頭] 欄位自行驗證。 授權標頭會包含驗證配置,以及該配置所需的適當回應。 例如,如果用戶端收到驗證回應標頭 「WWW-Authenticate: Basic Realm=」example「,則會將標頭 」Authorization: Basic < username:password > 「 新增至要求,並重新傳送至伺服器。

驗證配置有兩種一般類型:

  • 基本驗證配置,其中使用者名稱和密碼會以純文字傳送至伺服器。
  • 挑戰-回應配置,允許挑戰-回應格式。

基本驗證配置是以用戶端必須針對每個領域的使用者名稱和密碼自行驗證的模型為基礎。 如果伺服器是以包含有效使用者名稱和密碼的 Authorization 標頭重新傳送,則伺服器會服務要求。

挑戰回應配置可啟用更安全的驗證。 如果要求需要使用挑戰-回應配置進行驗證,則會將適當的狀態碼和驗證標頭傳回給用戶端。 然後,用戶端必須以交涉重新傳送要求。 伺服器會傳回具有挑戰的適當狀態碼,而用戶端接著會要求以適當的回應重新傳送要求,以取得要求的服務。

下表列出驗證配置、驗證類型、支援的 DLL,以及配置的描述。

配置 類型 DLL 描述
基本 (純文字) basic Wininet.dll 使用包含使用者名稱和密碼的 base64 編碼字串。
Digest challenge-response Digest.dll 挑戰回應配置,會挑戰使用 nonce (伺服器指定的資料字串) 值。 有效的回應包含使用者名稱、密碼、指定 nonce 值、HTTP 方法和要求的統一資源識別項 (URI) 總和檢查碼。 摘要式驗證支援是在 Microsoft Internet Explorer 5 中引進。
NT LAN Manager (NTLM) challenge-response Winsspi.dll 挑戰回應配置,以使用者名稱作為挑戰基礎。
Microsoft Network (MSN) challenge-response Msnsspc.dll Microsoft 網路的驗證配置。
分散式密碼驗證 (DPA) challenge-response Msapsspc.dll 類似于 MSN 驗證,也由 Microsoft 網路使用。
遠端複雜密碼驗證 (RPA) CompuServe Rpawinet.dll,da.dll CompuServe 驗證配置。 如需詳細資訊,請參閱 RPA 機制規格

 

除了基本驗證以外,還必須設定登錄機碼,才能安裝適當的 DLL。

如果需要驗證,應該在對 HttpOpenRequest的呼叫中使用INTERNET_FLAG_KEEP_CONNECTION旗標。 NTLM 和其他類型的驗證都需要INTERNET_FLAG_KEEP_CONNECTION旗標,才能在完成驗證程式時維護連線。 如果未維護連線,則必須使用 Proxy 或伺服器重新開機驗證程式。

即使需要驗證, InternetOpenUrlHttpSendRequest 函式仍可順利完成。 差別在於,標頭檔案和 InternetReadFile 中傳回的資料會收到 HTML 頁面,告知使用者狀態碼。

註冊驗證金鑰

INTERNET_OPEN_TYPE_PRECONFIG查看 ProxyEnableProxyServerProxyOverride 的登錄值。 這些值位於HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\網際網路設定底下。

對於基本以外的驗證配置,必須將金鑰新增至登錄的 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\安全性]。 DWORDFlags應該使用適當的值來設定。 下列清單顯示 Flags 值的可能值。

  • PLUGIN_AUTH_FLAGS_UNIQUE_CONTEXT_PER_TCPIP (value=0x01)

    每個傳輸控制通訊協定/網際網路通訊協定 (TCP/IP) 通訊端都包含不同的內容。 否則,會針對每個領域或區塊 URL 範本傳遞新的內容。

  • PLUGIN_AUTH_FLAGS_CAN_HANDLE_UI (value=0x02)

    此 DLL 可以處理自己的使用者輸入。

  • PLUGIN_AUTH_FLAGS_CAN_HANDLE_NO_PASSWD (value=0x04)

    此 DLL 可能無法執行驗證,而不提示使用者輸入密碼。

  • PLUGIN_AUTH_FLAGS_NO_REALM (value=0x08)

    此 DLL 不會使用標準 HTTP 領域字串。 任何看似領域的資料都是配置特定的資料。

  • PLUGIN_AUTH_FLAGS_KEEP_ALIVE_NOT_REQUIRED (value=0x10)

    此 DLL 不需要持續連線,以取得其挑戰-回應順序。

例如,若要新增 NTLM 驗證,必須將金鑰 NTLM 新增至HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\安全性。 在[\ HKEY_LOCAL_MACHINESOFTWARE\Microsoft\Internet Explorer\Security\NTLM] 下,必須新增字串值、DLLFileDWORDFlagsDLLFile 必須設定為 Winsspi.dll, 而 Flags 必須設定為 0x08。

伺服器驗證

當伺服器收到需要驗證的要求時,伺服器會傳回 401 狀態碼訊息。 在該訊息中,伺服器應該包含一或多個WWW-Authenticate回應標頭。 這些標頭包含伺服器可用的驗證方法。 WinINet 會選擇它所辨識的第一個方法。

除非通道第一次使用 SSL 或 PCT 加密連結,否則基本驗證會提供弱式安全性。

InternetErrorDlg函式可用來從使用者取得使用者名稱和密碼資料,或自訂的使用者介面設計來取得資料。

自訂介面可以使用 InternetSetOption 函式來設定 INTERNET_OPTION_PASSWORDINTERNET_OPTION_USERNAME 值,然後將要求重新傳送至伺服器。

Proxy 驗證

當用戶端嘗試使用需要驗證的 Proxy 時,Proxy 會將 407 狀態碼訊息傳回給用戶端。 在該訊息中,Proxy 應該包含一或多個Proxy-Authenticate回應標頭。 這些標頭包含 Proxy 提供的驗證方法。 WinINet 會選擇它所辨識的第一個方法。

InternetErrorDlg函式可用來從使用者取得使用者名稱和密碼資料,也可以設計自訂的使用者介面。

自訂介面可以使用 InternetSetOption 函式來設定 INTERNET_OPTION_PROXY_PASSWORDINTERNET_OPTION_PROXY_USERNAME 值,然後將要求重新傳送至 Proxy。

如果未設定 Proxy 使用者名稱和密碼,WinINet 會嘗試使用伺服器的使用者名稱和密碼。 此行為可讓用戶端實作用來處理伺服器驗證的相同自訂使用者介面。

處理 HTTP 驗證

您可以使用 InternetErrorDlg 或使用 InternetSetOption 的自訂函式,或新增自己的驗證標頭來處理 HTTP 驗證。 InternetErrorDlg 可以檢查與 HINTERNET 控制碼相關聯的標頭,以尋找隱藏的錯誤,例如 Proxy 或伺服器的狀態碼。 InternetSetOption 可用來設定 Proxy 和伺服器的使用者名稱和密碼。 針對 MSN 和 DPA 驗證, 必須使用 InternetErrorDlg 來設定使用者名稱和密碼。

對於新增自己的WWW-Authenticate或Proxy-Authenticate標頭的任何自訂函式,應該將 INTERNET_FLAG_NO_AUTH 旗標設定為停用驗證。

下列範例示範如何使用 InternetErrorDlg 來處理 HTTP 驗證。

HINTERNET hOpenHandle,  hConnectHandle, hResourceHandle;
DWORD dwError, dwErrorCode;
HWND hwnd = GetConsoleWindow();

hOpenHandle = InternetOpen(TEXT("Example"),
                           INTERNET_OPEN_TYPE_PRECONFIG, 
                           NULL, NULL, 0);

hConnectHandle = InternetConnect(hOpenHandle,
                                 TEXT("www.server.com"), 
                                 INTERNET_INVALID_PORT_NUMBER,
                                 NULL,
                                 NULL, 
                                 INTERNET_SERVICE_HTTP,
                                 0,0);

hResourceHandle = HttpOpenRequest(hConnectHandle, TEXT("GET"),
                                  TEXT("/premium/default.htm"),
                                  NULL, NULL, NULL, 
                                  INTERNET_FLAG_KEEP_CONNECTION, 0);

resend:

HttpSendRequest(hResourceHandle, NULL, 0, NULL, 0);

// dwErrorCode stores the error code associated with the call to
// HttpSendRequest.  

dwErrorCode = hResourceHandle ? ERROR_SUCCESS : GetLastError();

dwError = InternetErrorDlg(hwnd, hResourceHandle, dwErrorCode, 
                           FLAGS_ERROR_UI_FILTER_FOR_ERRORS | 
                           FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS |
                           FLAGS_ERROR_UI_FLAGS_GENERATE_DATA,
                           NULL);

if (dwError == ERROR_INTERNET_FORCE_RETRY)
    goto resend;

// Insert code to read the data from the hResourceHandle
// at this point.

在此範例中,dwErrorCode 可用來儲存與 HttpSendRequest呼叫相關聯的任何錯誤。 HttpSendRequest 成功完成,即使 Proxy 或伺服器需要驗證也一樣。 當FLAGS_ERROR_UI_FILTER_FOR_ERRORS旗標傳遞至 InternetErrorDlg時,函式會檢查標頭是否有任何隱藏的錯誤。 這些隱藏的錯誤會包含驗證的任何要求。 InternetErrorDlg 會顯示適當的對話方塊,以提示使用者輸入必要的資料。 FLAGS_ERROR_UI_FLAGS_GENERATE_DATA和FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS旗標也應該傳遞至 InternetErrorDlg,讓函式為錯誤建構適當的資料結構,並將對話方塊的結果儲存在 HINTERNET 控制碼中。

下列範例程式碼示範如何使用 InternetSetOption處理驗證。

HINTERNET hOpenHandle,  hResourceHandle, hConnectHandle;
DWORD dwStatus;
DWORD dwStatusSize = sizeof(dwStatus);
char strUsername[64], strPassword[64];

// Normally, hOpenHandle, hResourceHandle,
// and hConnectHandle need to be properly assigned.

hOpenHandle = InternetOpen(TEXT("Example"),
                           INTERNET_OPEN_TYPE_PRECONFIG,
                           NULL, NULL, 0);
hConnectHandle = InternetConnect(hOpenHandle,
                                 TEXT("www.server.com"),
                                 INTERNET_INVALID_PORT_NUMBER,
                                 NULL,
                                 NULL,
                                 INTERNET_SERVICE_HTTP,
                                 0,0);

hResourceHandle = HttpOpenRequest(hConnectHandle, TEXT("GET"),
                                  TEXT("/premium/default.htm"),
                                  NULL, NULL, NULL,
                                  INTERNET_FLAG_KEEP_CONNECTION,
                                  0);

resend:

HttpSendRequest(hResourceHandle, NULL, 0, NULL, 0);

HttpQueryInfo(hResourceHandle, HTTP_QUERY_FLAG_NUMBER |
              HTTP_QUERY_STATUS_CODE, &dwStatus, &dwStatusSize, NULL);

switch (dwStatus)
{
    // cchUserLength is the length of strUsername and
    // cchPasswordLength is the length of strPassword.
    DWORD cchUserLength, cchPasswordLength;

    case HTTP_STATUS_PROXY_AUTH_REQ: // Proxy Authentication Required
        // Insert code to set strUsername and strPassword.

        // Insert code to safely determine cchUserLength and
        // cchPasswordLength. Insert appropriate error handling code.
        InternetSetOption(hResourceHandle,
                          INTERNET_OPTION_PROXY_USERNAME,
                          strUsername,
                          cchUserLength+1);

        InternetSetOption(hResourceHandle,
                          INTERNET_OPTION_PROXY_PASSWORD,
                          strPassword,
                          cchPasswordLength+1);
        goto resend;
        break;

    case HTTP_STATUS_DENIED:     // Server Authentication Required.
        // Insert code to set strUsername and strPassword.

        // Insert code to safely determine cchUserLength and
        // cchPasswordLength. Insert error handling code as
        // appropriate.
        InternetSetOption(hResourceHandle, INTERNET_OPTION_USERNAME,
                          strUsername, cchUserLength+1);
        InternetSetOption(hResourceHandle, INTERNET_OPTION_PASSWORD,
                          strPassword, cchPasswordLength+1);
        goto resend;
        break;
}

// Insert code to read the data from the hResourceHandle
// at this point.

注意

WinINet 不支援伺服器實作。 此外,它不應該從服務使用。 對於伺服器實作或服務,請使用 Microsoft Windows HTTP 服務 (WinHTTP)