如何在请求失败后重置WinHTTP代理凭据?

时间:2014-10-02 15:24:06

标签: windows winapi winhttp

我需要编写代码来下载文件,要求如下。如果应用程序配置为使用代理,请尝试通过代理下载。如果失败,请尝试直接连接。如果未配置代理,请尝试直接连接。高级伪代码:

if(ProxyEnabled)
    if(!DownloadWithProxy())
        DownloadWithoutProxy()
else
    DownloadWithoutProxy()

我正在使用WinHTTP,因为此代码将在服务中运行。实际下载很简单,但我遇到代理设置问题。我目前的伪代码如下:

hSession = WinHttpOpen(..., WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, ...)
hConnect = WinHttpConnect(hSession, ...)
hRequest = WinHttpOpenRequest(hConnect, ...)
if(Proxy.Enabled)
{
    // set proxy server
    WinHttpSetOption(hRequest, WINHTTP_OPTION_PROXY, ServerName)
    // set proxy credentials
    WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_PROXY, UserName, Password)
    if(!DownloadFile(hRequest))
    {
        // reset proxy server
        WinHttpSetOption(hRequest, WINHTTP_OPTION_PROXY, NULL)
        // reset proxy credentials
        WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_PROXY, NULL, NULL)
        DownloadFile(hRequest)
    }
}
else
{
    DownloadFile(hRequest)
}

DownloadFile()WinHttpSendRequest()序列WinHttpReceiveResponse()的位置。除了通过代理下载失败的情况外,一切正常。发生这种情况时,调用WinHttpSetCredentials()重置凭据失败(使用ERROR_INVALID_PARAMETER),因此第二次调用DownloadFile()仍尝试使用代理(即使我重置了它)和凭据。注意:在此特定方案中,我使用有效的代理服务器但无效的代理凭据来触发失败。

所以我想我的问题是什么是最好的方法?据我所知,无法通过WinHttpSetCredentials()重置凭据集,所以我想我应该重新创建每次调用DownloadFile()的请求,而不是通过重用来尝试“聪明”一个请求对象。

1 个答案:

答案 0 :(得分:1)

WinHttpSetCredentials()需要6个参数,其中一个是auth方案。您只显示了4个参数,并且没有说明您正在使用哪个身份验证方案。总的来说,您的身份验证顺序与MSDN建议的顺序不同:

Authentication in WinHTTP

  

典型的WinHTTP应用程序完成以下步骤以处理身份验证。

     

•使用WinHttpOpenRequest和WinHttpSendRequest请求资源   •使用WinHttpQueryHeaders检查响应头   •如果返回401或407状态代码,表明需要进行身份验证,请调用WinHttpQueryAuthSchemes以查找可接受的方案。
  •使用WinHttpSetCredentials设置身份验证方案,用户名和密码   •通过调用WinHttpSendRequest重新发送具有相同请求句柄的请求。

还要注意这个条件:

  

WinHttpSetCredentials设置的凭据仅用于一个请求。 WinHTTP不会缓存要在其他请求中使用的凭据,这意味着必须编写可以响应多个请求的应用程序。如果重新使用经过身份验证的连接,则其他请求可能不会受到质疑,但您的代码应该能够随时响应请求。

上面链接的文档包含以下有关如何正确使用WinHTTPSetCredentials()的代码示例:

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

您只需在适当的时候注入WinHttpSetOption(WINHTTP_OPTION_PROXY),并确保您正在处理代理和非代理连接的身份验证请求(如果目标HTTP服务器需要自己的身份验证)。