处理身份验证

某些代理和服务器要求在授予对 Internet 上的资源的访问权限之前进行身份验证。 WinINet 函数支持 http 会话的服务器和代理身份验证。 ftp 服务器的身份验证必须由 InternetConnect 函数处理。 目前不支持 FTP 网关身份验证。

关于 HTTP 身份验证

如果需要身份验证,客户端应用程序将收到状态代码 401(如果服务器需要身份验证),如果代理需要身份验证,则会收到状态代码 407。 使用状态代码,代理或服务器发送一个或多个身份验证响应标头-代理身份验证 (用于代理身份验证) 或服务器身份验证) WWW-Authenticate (。

每个身份验证响应标头都包含可用的身份验证方案和领域。 如果支持多个身份验证方案,服务器将返回多个身份验证响应标头。 领域值区分大小写,定义代理或服务器上的保护空间。 例如,标头“WWW-Authenticate: Basic Realm=”example“是需要服务器身份验证时返回的标头的示例。

发送请求的客户端应用程序可以通过在请求中包含 Authorization 标头字段来对自身进行身份验证。 授权标头将包含身份验证方案以及该方案所需的相应响应。 例如,如果客户端收到身份验证响应标头“WWW-Authenticate: Basic Realm=”example“,则将标头”Authorization: Basic <username:password>“添加到请求并重新发送到服务器。

有两种常规类型的身份验证方案:

  • 基本身份验证方案,其中用户名和密码以明文形式发送到服务器。
  • 质询-响应方案,允许质询-响应格式。

基本身份验证方案基于客户端必须使用每个领域的用户名和密码对自身进行身份验证的模型。 如果请求是使用包含有效用户名和密码的 Authorization 标头重新发送的,则服务器会为该请求提供服务。

质询-响应方案可实现更安全的身份验证。 如果请求需要使用质询-响应方案进行身份验证,则会将相应的状态代码和 Authenticate 标头返回到客户端。 然后,客户端必须通过协商重新发送请求。 服务器将返回带有质询的相应状态代码,然后客户端会要求使用适当的响应重新发送请求以获取请求的服务。

下表列出了身份验证方案、身份验证类型、支持它们的 DLL 以及方案的说明。

Scheme 类型 DLL 说明
基本 (明文) 基本 Wininet.dll 使用包含用户名和密码的 base64 编码字符串。
摘要 challenge-response Digest.dll 质询-响应方案,使用 nonce (服务器指定的数据字符串) 值进行质询。 有效响应包含用户名、密码、给定 nonce 值、HTTP 方法以及请求的统一资源标识符 (URI) 的校验和。 Microsoft Internet Explorer 5 中引入了摘要式身份验证支持。
NT LAN Manager (NTLM) challenge-response Winsspi.dll 基于用户名的质询-响应方案。
Microsoft 网络 (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标志,以便在完成身份验证过程时保持连接。 如果未保持连接,则必须使用代理或服务器重启身份验证过程。

即使需要身份验证, InternetOpenUrlHttpSendRequest 函数也会成功完成。 区别在于,在头文件和 InternetReadFile 中返回的数据将收到一个 HTML 页面,通知用户状态代码。

注册身份验证密钥

INTERNET_OPEN_TYPE_PRECONFIG查看注册表值 ProxyEnableProxyServerProxyOverride。 这些值位于 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet 设置下。

对于除“基本”以外的身份验证方案,必须将密钥添加到 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Security 下的注册表中。 应使用适当的值设置 DWORDFlags。 以下列表显示了 Flags 值的可能值。

  • PLUGIN_AUTH_FLAGS_UNIQUE_CONTEXT_PER_TCPIP (value=0x01)

    TCP/IP) 套接字 (传输控制协议/Internet 协议都包含不同的上下文。 否则,将为每个领域或块 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 下,必须添加字符串值 DLLFileDWORDFlagsDLLFile 必须设置为 Winsspi.dll, 标志 必须设置为 0x08。

服务器身份验证

当服务器收到需要身份验证的请求时,服务器将返回 401 状态代码消息。 在该消息中,服务器应包含一个或多个WWW-Authenticate响应标头。 这些标头包括服务器可用的身份验证方法。 WinINet 选择它识别的第一种方法。

基本身份验证提供弱安全性,除非通道首先使用 SSL 或 PCT 进行链接加密。

InternetErrorDlg 函数可用于从用户获取用户名和密码数据,也可以将自定义用户界面设计为获取数据。

自定义接口可以使用 InternetSetOption 函数设置 INTERNET_OPTION_PASSWORDINTERNET_OPTION_USERNAME 值,然后将请求重新发到服务器。

代理身份验证

当客户端尝试使用需要身份验证的代理时,代理会向客户端返回 407 状态代码消息。 在该消息中,代理应包含一个或多个Proxy-Authenticate响应标头。 这些标头包括代理提供的身份验证方法。 WinINet 选择它识别的第一种方法。

InternetErrorDlg 函数可用于从用户获取用户名和密码数据,也可以设计自定义的用户界面。

自定义接口可以使用 InternetSetOption 函数设置 INTERNET_OPTION_PROXY_PASSWORDINTERNET_OPTION_PROXY_USERNAME 值,然后将请求重新发到代理。

如果未设置代理用户名和密码,WinINet 将尝试使用服务器的用户名和密码。 此行为使客户端可以实现用于处理服务器身份验证的相同自定义用户界面。

处理 HTTP 身份验证

可以使用 InternetErrorDlg 或使用 InternetSetOption 的自定义函数或添加自己的身份验证标头来处理 HTTP 身份验证。 InternetErrorDlg 可以检查与 HINTERNET 句柄关联的标头,以查找隐藏的错误,例如来自代理或服务器的状态代码。 InternetSetOption 可用于设置代理服务器和服务器的用户名和密码。 对于 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 成功完成,即使代理或服务器需要身份验证。 将 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 Services (WinHTTP)