處理驗證
某些 Proxy 和伺服器需要驗證,才能授與因特網上資源的存取權。 WinINet 函式支援 HTTP 工作階段的伺服器和代理伺服器驗證。 ftp 伺服器的驗證必須由 InternetConnect 函式處理。 目前不支援 FTP 閘道驗證。
關於 HTTP 驗證
如果需要驗證,如果伺服器需要驗證,用戶端應用程式會收到狀態代碼 401,如果 Proxy 需要驗證,則為 407。 使用狀態代碼時,Proxy 或伺服器會傳送一或多個驗證回應標頭—Proxy-Authenticate(用於 Proxy 驗證)或 WWW-Authenticate(用於伺服器驗證)。
每個驗證響應標頭都包含可用的驗證配置和領域。 如果支援多個驗證配置,伺服器會傳回多個驗證響應標頭。 領域值會區分大小寫,並定義 Proxy 或伺服器上的保護空間。 例如,當需要伺服器驗證時,標頭 「WWW-Authenticate: Basic Realm=\"example\"」 就是傳回的標頭範例。
傳送請求的客戶端應用程式可以通過在請求中包含 Authorization 標頭欄位來進行身份驗證。 授權標頭會包含驗證配置,以及該配置所需的適當回應。 例如,如果用戶端收到驗證響應標頭 "WWW-Authenticate: Basic Realm=\"example\"" ,則標頭 "Authorization: Basic <username:password>" 會被新增至要求中,並重新傳送至伺服器。
驗證設定有兩種一般類型:
- 基本身份驗證配置,其中使用者名稱和密碼會以純文本傳送至伺服器。
- 挑戰回應方案,允許挑戰回應格式。
基本身份驗證配置是以客戶端必須針對每個領域使用使用者名稱和密碼驗證本身的模型為基礎。 如果伺服器以包含有效使用者名稱和密碼的 Authorization 標頭重新傳送要求,則伺服器會提供服務。
挑戰回應方案能提供更安全的驗證。 如果要求需要使用挑戰回應配置進行驗證,則會將適當的狀態代碼和驗證標頭傳回給用戶端。 然後,客戶端必須帶有協商參數重新傳送請求。 伺服器會傳回具有挑戰的適當狀態代碼,然後用戶端會要求以適當的回應重新傳送要求,以取得要求的服務。
下表列出驗證配置、驗證類型、支援的 DLL,以及配置的描述。
方案 | 類型 | DLL | 描述 |
---|---|---|---|
基本 (純文字) | 基本 | Wininet.dll | 使用包含使用者名稱和密碼的base64編碼字串。 |
摘要 | 挑戰-回應機制 | Digest.dll | 挑戰回應機制,使用 nonce(伺服器指定的數據字串)值進行挑戰。 校驗碼包含使用者名稱、密碼、指定的 nonce 值、HTTP 方法,以及所要求的統一資源識別碼(URI)。 Microsoft Internet Explorer 5 中引進了摘要式驗證支援。 |
NT LAN 管理員 (NTLM) | 質疑-回應 | Winsspi.dll | 根據使用者名稱進行挑戰的挑戰回應機制。 |
Microsoft網络 (MSN) | 質詢-回應 | Msnsspc.dll | Microsoft網路的驗證配置。 |
分散式密碼驗證 (DPA) | 質詢-應答 | Msapsspc.dll | 類似於 MSN 驗證,Microsoft網路也會使用。 |
遠端密碼短語驗證 (RPA) | CompuServe | Rpawinet.dll、da.dll | CompuServe 驗證機制。 如需詳細資訊,請參閱 RPA 機制規格。 |
對於基本身份驗證以外的任何專案,除了安裝適當的 DLL 之外,還必須設定登錄機碼。
如果需要驗證,INTERNET_FLAG_KEEP_CONNECTION 旗標應該用於呼叫 HttpOpenRequest。 NTLM 和其他類型的驗證都需要INTERNET_FLAG_KEEP_CONNECTION旗標,才能在完成驗證程式時維持連線。 如果未維護連線,則必須使用 Proxy 或伺服器重新啟動驗證程式。
InternetOpenUrl 和 HttpSendRequest 函式即使在需要驗證時也順利完成。 差別在於,在頭檔中傳回的數據,InternetReadFile 會收到一個 HTML 頁面,告知用戶狀態代碼。
註冊驗證金鑰
INTERNET_OPEN_TYPE_PRECONFIG 會查看註冊表值 ProxyEnable、ProxyServer和 ProxyOverride。 這些值位於 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings之下。
對於 Basic 以外的驗證配置,必須在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Security下將密鑰新增至登錄。 DWORD 值,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\Security。 在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Security\NTLM、字符串值、DLLFile,以及 DWORD 值,Flags必須新增。 DLLFile 必須設定為 Winsspi.dll,而且 旗標 必須設定為 0x08。
伺服器驗證
當伺服器收到需要驗證的要求時,伺服器會傳回 401 狀態代碼訊息。 在該訊息中,伺服器應該包含一或多個 WWW-Authenticate 回應標頭。 這些標頭包含伺服器可用的驗證方法。 WinINet 會選擇它辨識的第一個方法。
除非通道第一次使用 SSL 或 PCT 加密連結,否則基本身份驗證會提供弱式安全性。
InternetErrorDlg 函式可用來從使用者取得使用者名稱和密碼數據,或設計自定義的使用者介面來取得數據。
自定義介面可以使用 InternetSetOption 函式來設定 INTERNET_OPTION_PASSWORD 和 INTERNET_OPTION_USERNAME 值,然後將要求重新傳送至伺服器。
代理伺服器認證
當客戶端嘗試使用需要驗證的 Proxy 時,Proxy 會將 407 狀態代碼訊息傳回給用戶端。 在該訊息中,Proxy 應該包含一或多個 Proxy-Authenticate 回應標頭。 這些標頭包含 Proxy 中可用的驗證方法。 WinINet 會選擇它辨識的第一個方法。
InternetErrorDlg 函式可用來從使用者取得使用者名稱和密碼數據,也可以設計自定義的使用者介面。
自定義介面可以使用 InternetSetOption 函式來設定 INTERNET_OPTION_PROXY_PASSWORD 和 INTERNET_OPTION_PROXY_USERNAME 值,然後將要求重新傳送至 Proxy。
如果未設定 Proxy 使用者名稱和密碼,WinINet 會嘗試使用伺服器的使用者名稱和密碼。 客戶端透過此機制可以實現與處理伺服器驗證相同的自定義使用者介面。
處理 HTTP 驗證
HTTP 驗證可以透過 InternetErrorDlg,或是透過使用 InternetSetOption 自訂函式,或者新增自己的驗證標頭來處理。 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)。