WinHTTP中的认证问题

WinHTTP中的认证问题

Some HTTP servers and proxies require authentication before allowing access to resources on the Internet. The Microsoft Windows HTTP Services (WinHTTP) functions support server and proxy authentication for HTTP sessions.

一些HTTP服务器和代理在访问前需要认证。WinHTTP函数支持HTTP会话中的服务器和代理认证。

关于HTTP认证

If authentication is required, the HTTP application receives a status code of 401 (server requires authentication) or 407 (proxy requires authentication). Along with the status code, the proxy or server sends one or more authenticate headers: WWW-Authenticate (for server authentication) or Proxy-Authenticate (for proxy authentication).

如果要求认证,HTTP程序会受到一个401(服务器需要认证)状态码或407(代理需要认证)。伴随这些状态码,代理或服务器会发送一个或多个认证头部:WWW-Authenticate (用于服务器认证) 或 Proxy-Authenticate (代理需要认证).

Each authenticate header contains a supported authentication scheme and, for the Basic and Digest schemes, a realm. If multiple authentication schemes are supported, the server returns multiple authenticate headers. The realm value is case-sensitive and defines a set of servers or proxies for which the same credentials are accepted. For example, the header "WWW-Authenticate: Basic Realm="example"" might be returned when server authentication is required. This header specifies that user credentials must be supplied for the "example" domain.

每个认证头部包含一种支持的认证方式如Basic 认证和Digest 认证, 或 realm认证。如果多种认证方式都支持,服务器会返回多个认证头部。realm值是大小写敏感的,定义了一组服务器或代理,这些代理接受相同的凭证。例如,形如"WWW-Authenticate: Basic Realm="example""的头部会被需要认证服务器返回。这个头部指定了用户必须提供"example"域下的凭证。

An HTTP application can include an authorization header field with a request it sends to the server. The authorization header contains the authentication scheme and the appropriate response required by that scheme. For example, the header "Authorization: Basic " would be added to the request and sent to the server if the client received the response header "WWW-Authenticate: Basic Realm="example"".

一个HTTP程序向服务器发信息时可以包含一个认证头部域。这个认证头部包含认证格式和匹配的的响应。例如,如果客户端收到"WWW-Authenticate: Basic Realm="example"",后应该向服务器发送添加了"Authorization: Basic "头部信息。

Note  Although they are shown here as plain text, the username and password are actually base64 encoded.
注意,虽然这里显示的是明文信息,用户名和密码实际需要进行 base64编码.

There are two general types of authentication schemes:

一般有两种常见的认证方式:

  • Basic authentication scheme, in which the user name and password are sent in clear text to the server.

    The Basic authentication scheme is based on the model that a client must identify itself with a user name and password for each realm. The server services the request only if the request is sent with an authorization header that includes a valid user name and password.

  • Basic认证方式,这种方式用户名和密码都是以明文形式发送给服务器的。Basic认证方式是基于一种模型,这种模型要求客户必须每次都要标明自己的用户名和密码。服务器只对带有有效用户名和密码认证头部的客户进行响应。
  • Challenge-response schemes, such as Kerberos, in which the server challenges the client withauthentication data. The client transforms the data with the user credentials and sends the transformed data back to the server for authentication.

    Challenge-response schemes enable a more secure authentication. In a challenge-response scheme, the username and password are never transmitted over the network. After the client selects a challenge-response scheme, the server returns an appropriate status code with a challenge that contains the authentication data for that scheme. The client then resends the request with the proper response to obtain the requested service. Challenge-response schemes can take multiple exchanges to complete.

  • 挑战-响应方式,如Kerberos,这种方式中,服务器首先向客户用authentication data发出挑战。客户利用它的凭据对挑战信息进行处理,然后把处理过的信息发挥给服务器完成认证。挑战-响应可以实现更安全的认证。在这种机制中,因为用户名和密码不会出现在网络中。客户选择挑战-响应机制,服务器返回对应的状态码,并把包含authentication data信息发给客户。客户然后重新用对应的响应向服务器发送请求。挑战-响应方式可以经过多次交换来完成。

The following table contains the authentication schemes that are supported by WinHTTP, the authentication type, and a description of the scheme.

下表显示了WinHTTP支持的认证方式。

方式 类型 描述

Basic (plaintext)

Basic

Uses a base64 encoded string that contains the user name and password.

使用经过BASE64编码的用户名和密码。

Digest Challenge-response

Challenges using a nonce (a server-specified data string) value. A valid response contains a checksum of the user name, the password, the given nonce value, theHTTP verb, and the requested Uniform Resource Identifier (URI).

服务器使用一个挑战(服务器指定的字符串)值。一个有效的响应包含用户名、密码和挑战值的校验和,HTTP verb和请求URI。

NTLM Challenge-response

Requires the authentication data to be transformed with the user credentials to prove identity. For NTLM authentication to function correctly, several exchanges must take place on the same connection. Therefore, NTLM authentication cannot be used if an intervening proxy does not support keep-alive connections. NTLM authentication also fails ifWinHttpSetOption is used with the WINHTTP_DISABLE_KEEP_ALIVE flag that disables keep-alive semantics.

要求把挑战信息利用用户凭证进行转换作为标识符。例如要使用NTLM认证成功,一个连接必须发出数次交互。因此NTLM认证不能在不支持keep-alive连接的代理服务器中使用。如果WinHttpSetOption函数中如果使用了WINHTTP_DISABLE_KEEP_ALIVE标志NTLM认证也会失效,因为这个标志也禁止保持连接。

Passport Challenge-response

Uses Microsoft Passport 1.4.

参见Microsoft Passport 1.4.

Negotiate Challenge-response

If both the server and client are using Windows 2000 or later, Kerberos authentication is used. Otherwise NTLM authentication is used. Kerberos is available in Windows 2000 and later operating systems and is considered to be more secure than NTLM authentication. For Negotiate authentication to function correctly, several exchanges must take place on the same connection. Therefore, Negotiate authentication cannot be used if an intervening proxy does not support keep-alive connections. Negotiate authentication also fails if WinHttpSetOption is used with the WINHTTP_DISABLE_KEEP_ALIVE flag that disables keep-alive semantics. The Negotiate authentication scheme is sometimes called Integrated Windows authentication.

如果服务器和客户端都使用Windows2000以后操作系统,这些操作系统都使用了Kerberos。因此NTLM也已部署。Windows2000及以后的操作系统支持的Kerberos就是考虑到了要比NTLM更安全。要通过协商认证,服务器和客户端要发生数次交互,因此NTLM认证不能在不支持keep-alive连接的代理服务器中使用。如果WinHttpSetOption函数中如果使用了WINHTTP_DISABLE_KEEP_ALIVE标志NTLM认证也会失效,因为这个标志也禁止保持连接。协商认证机制有时也被称为Windows内部认证。

 

WinHTTP应用程序中的认证

The WinHTTP application programming interface (API) provides two functions used to access Internet resources in situations where authentication is required:WinHttpSetCredentials and WinHttpQueryAuthSchemes.

WinHTTP API提供了WinHttpSetCredentialsWinHttpQueryAuthSchemes两个函数接口来应对需要认证的场景。

When a response is received with a 401 or 407 status code, WinHttpQueryAuthSchemes can be used to parse the authentication headers to determine the supported authentication schemes and the authentication target. The authentication target is the server or proxy that requests authentication.WinHttpQueryAuthSchemes also determines the first authentication scheme, from the available schemes, based on the authentication scheme preferences suggested by the server. This method for choosing an authentication scheme is the behavior suggested by RFC 2616.

当收到401或407状态码时,WinHttpQueryAuthSchemes函数可被用来解析何种认证方式和认证目标是谁。认证目标是指是服务器认证还是代理认证。WinHttpQueryAuthSchemes也可探测出服务器提供的第一种认证方式。这种选择认证方式的方法在RFC 2616有提及。

WinHttpSetCredentials enables an application to specify the authentication scheme that is used along with a valid username and password for use on the target server or proxy. After setting the credentials and resending the request, the necessary headers are generated and added to the request automatically. Because some authentication schemes require multiple transactionsWinHttpSendRequest could return the error, ERROR_WINHTTP_RESEND_REQUEST. When this error is encountered, the application should continue to resend the request until a response is received that does not contain a 401 or 407 status code. A 200 status code indicates that the resource is available and the request is successful. SeeHTTP Status Codes for additional status codes that can be returned.

WinHttpSetCredentials使得应用程序可以设定对服务器或代理有效的用户名和密码及认证方式。设置好凭据后重新发送请求,所需的头部信息会自动添加到请求信息中。因为一些服务器或代理使用的认证方式需要多次交互,WinHttpSendRequest可能返回错误代码ERROR_WINHTTP_RESEND_REQUEST。当遇到这个错误时,程序需要继续发送请求直到收到不包含401或407的错误状态码。200的状态码表明资源有效、请求成功。查看HTTP Status Codes可以发现更多的返回状态码信息。

If an acceptable authentication scheme and credentials are known before a request is sent to the server, an application can callWinHttpSetCredentials before calling WinHttpSendRequest. In this case, WinHTTP attempts pre-authentication with the server by providing credentials orauthentication data in the initial request to the server. Pre-authentication can decrease the number of exchanges in the authentication process and therefore improve application performance.

如果已经知道服务器的认证方式和机制,我们可以通过在WinHttpSendRequest前调用WinHttpSetCredentials来处理。这种情况下WinHTTP尝试使用凭据或认证信息对服务器进行预认证,预认证可以减少客户与服务器的交互次数、提高程序性能。

Preauthentication can be used with the following authentication schemes:

与认证可以在下礼认证方式中使用。

  • Basic - 一般可以.
  • Negotiate resolving into Kerberos - 很有可能可以;唯一的例外是当客户和域控服务器之间产生了时间扭曲。
  • (Negotiate resolving into NTLM) - 完全不可能。
  • NTLM - 只在Windows Server 2008 R2 有可能.
  • Digest - 完全不可能.
  • Passport - 完全不可能; 经过初始的challenge-response, WinHTTP 使用cookies进行预认证。

A typical WinHTTP application completes the following steps in order to handle authentication.

典型的WinHTTP程序认证的常见步骤。

  • 使用 WinHttpOpenRequest WinHttpSendRequest请求资源
  • 使用 WinHttpQueryHeaders.查询响应头部。
  • 如果返回401 或 407,表明需要认证, 调用 WinHttpQueryAuthSchemes 找出可处理的认证方式。
  • 使用 WinHttpSetCredentials.设置认证方式,用户名,密码等。
  • 通过调用WinHttpSendRequest重新发送请求。

The credentials set by WinHttpSetCredentials are only used for one request. WinHTTP does not cache the credentials to use in other requests, which means that applications must be written that can respond to multiple requests. If an authenticated connection is re-used, other requests may not be challenged, but your code should be able to respond to a request at any time.

使用WinHttpSetCredentials设置的凭证只在一次请求中有效。WinHTTP不为其它请求缓存凭证,这就意味着程序应写成可以响应多次请求。如果一个认证连接可以被重用,其它请求必须每次挑战-响应,但你的代码应该能响应每次请求。

例子: 获取一份文档


The following sample code attempts to retrieve a specified document from an HTTP server. The status code is retrieved from the response to determine if authentication is required. If a 200 status code is found, the document is available. If a status code of 401 or 407 is found, authentication is required before the document can be retrieved. For any other status code, an error message is displayed. SeeHTTP Status Codes for a list of possible status codes.

下面例程尝试从HTTP服务器获取一个指定文档。从响应中获取的状态码来判定是否需要认证。如果一个200状态码被发现,说明文档状态已经有效。如果状态码是401或407,在访问文档前必须进行认证。如果是其他状态码,显示错误号。请查HTTP Status Codes确定可能的错误状态。

C++
#include 
#include 
#include 

#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 );
}



自动登录策略

The automatic logon (auto-logon) policy determines when it is acceptable for WinHTTP to include the default credentials in a request. The default credentials are either the current thread token or the session token depending on whether WinHTTP is used in synchronous or asynchronous mode. The thread token is used in synchronous mode, and the session token is used in asynchronous mode. These default credentials are often the username and password used to log on to Microsoft Windows.

自动登录(auto-logon)策略决定了WinHTTP什么时候在请求中使用默认策略。默认的凭据是当前线程的Token或会话的Token取决于WinHTTP实在同步状体或异步状态。线程Token用于同步方式,会话Token用于异步模式。这些默认的凭据一般是登录系统时用的用户名和密码。

The auto-logon policy was implemented to prevent these credentials from being casually used to authenticate against an untrusted server. By default, the security level is set to WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM, which allows the default credentials to be used only for Intranet requests. The auto-logon policy only applies to the NTLM and Negotiate authentication schemes. Credentials are never automatically transmitted with other schemes.

auto-logon策略实现了防止这些凭据被用于认证那些不受信任的服务器。默认的,安全级别被设置为WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM,这个及被允许默认凭证被用于内网认证。auto-logon策略只用于NTLM 和 Negotiate authentication。凭据不能被用于其它认证方式。

The auto-logon policy can be set using the WinHttpSetOption function with the WINHTTP_OPTION_AUTOLOGON_POLICY flag. This flag applies only to the request handle. When the policy is set to WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW, default credentials can be sent to all servers. When the policy is set to WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH, default credentials cannot be used for authentication. It is strongly recommended that you use the auto-logon at the MEDIUM level.

auto-logon策略可以通过WinHttpSetOption函数中的WINHTTP_OPTION_AUTOLOGON_POLICY参数设置,这个标识仅对请求句柄有效。当策略设置为WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW时,默认的凭据就可以发送给所有服务器,当策略设置为WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH时,默认凭证不能用于认证。强烈建议你把自动登录策略安全性设置为中。

存储的用户名和密码

Windows XP introduced the concept of Stored User Names and Passwords. If a user's Passport credentials are saved through thePassport Registration Wizard or the standard Credential Dialog, it is saved in the Stored User Names and Passwords. When using WinHTTP on Windows XP or later, WinHTTP automatically uses the credentials in the Stored User Names and Passwords if credentials are not explicitly set. This is similar to the support of default logon credentials for NTLM/Kerberos. However, use of default Passport credentials is not subject to the automatic logon policy settings.

WindowsXP介绍了存储的用户名和密码的概念。如果一个用户凭证通过认证注册向导或标准凭证对话框,就会被保存为默认用户名和密码。当在Windows XP或更高版本中使用WinHTTP时,WinHTTP会自动使用存储的凭证如果凭证没有显式设置。这有点像NTLM/Kerberos中的默认登录凭证。然而,使用默认登录凭证不守自动登录策略影响。

你可能感兴趣的:(C++)