2010-07-03 8 views
17

Varios artículos (1, 2) he descubierto esta mirada bastante fácil:¿Cómo puedo hacer la autenticación resumida con HttpWebRequest?

WebRequest request = HttpWebRequest.Create(url); 

var credentialCache = new CredentialCache(); 
credentialCache.Add(
    new Uri(url), // request url 
    "Digest", // authentication type 
    new NetworkCredential("user", "password") // credentials 
); 

request.Credentials = credentialCache; 

Sin embargo, esto sólo funciona para las URL sin parámetros de URL. Por ejemplo, puedo descargar http://example.com/test/xyz.html muy bien, pero cuando intento descargar http://example.com/test?page=xyz, el resultado es un mal mensaje 400 de solicitud con la siguiente en los registros del servidor (que operan con Apache 2.2):

Digest: uri mismatch - </test> does not match request-uri </test?page=xyz> 

Mi primera idea era que la especificación de resumen requiere que se eliminen los parámetros de URL del hash de resumen, pero eliminar el parámetro de la URL pasada a credentialCache.Add() no cambió nada. Por lo tanto, debe ser al revés y en algún lugar del marco .NET está eliminando erróneamente el parámetro de la URL.

+0

Aquí hay una pregunta similar sobre SO, mi búsqueda inicial no apareció: http://stackoverflow.com/questions/3109507/httpwebrequests-sends-parameterless-uri-in-authorization-header – Cygon

+0

Y un Microsoft Connect informe de error: https://connect.microsoft.com/VisualStudio/feedback/details/571052/digest-authentication-does-not-send-the-full-uri-path-in-the-uri-parameter – Cygon

+0

The Microsoft Connect informe de error vinculado anteriormente parece tener una solución, publicado el 26/06. ¿Has probado eso? –

Respuesta

0

Creo que la segunda URL apunta a la página dinámica y primero debe llamarla usando GET para obtener el HTML y luego descargarlo. Sin embargo, no hay experiencia en este campo.

+0

Lo siento, no. Depende completamente del servidor web qué hacer con la URL y la primera página también podría ser dinámica. Además, lo que se descarga es HTML, no hay diferencia entre descargar HTML o descargar algo más. – Cygon

4

Dijiste que eliminaste los parámetros de la cadena de consulta, pero ¿intentabas volver solo al host? Cada ejemplo de CredentialsCache.Add() que he visto parece usar solo el host, y los documentos para CredentialsCache.Add() enumeran el parámetro Uri como "uriPrefix", lo que parece revelador.

En otras palabras, pruebe esto:

Uri uri = new Uri(url); 
WebRequest request = WebRequest.Create(uri); 

var credentialCache = new CredentialCache(); 
credentialCache.Add( 
    new Uri(uri.GetLeftPart(UriPartial.Authority)), // request url's host 
    "Digest", // authentication type 
    new NetworkCredential("user", "password") // credentials 
); 

request.Credentials = credentialCache; 

Si esto funciona, también tendrá que asegurarse de que no se agrega la misma "autoridad" a la caché más de una vez ... todas las solicitudes al mismo host deberían poder utilizar la misma entrada de caché de credenciales.

+0

Extraño, no encontré un solo ejemplo utilizando solo el URI raíz para la autenticación. En cualquier caso, no funciona, lo siento. Según la sección 3.2.2 de RFC 2617 (http://rfc.askapache.com/rfc2617/rfc2617.html#section-3.2.2), el URI de resumen debe ser idéntico al 'request-uri' en la solicitud HTTP. – Cygon

+0

Aquí hay algunos ejemplos: http://msdn.microsoft.com/en-us/library/system.net.credentialcache.aspx, http://support.microsoft.com/kb/822456, http: // blogs. msdn.com/b/buckh/archive/2004/07/28/199706.aspx (aunque es cierto que es un ejemplo de 'localhost'). – JaredReisinger

+0

Sí, el RFC dice que el digest-uri debe coincidir con la solicitud, pero eso es lo que se envía por cable, no lo que está almacenado en el caché. El CredentialCache.GetCredential() doc (http://msdn.microsoft.com/en-us/library/fy4394xd.aspx) dice que "GetCredential usa el prefijo URI coincidente más largo en la memoria caché para determinar qué conjunto de credenciales devolver para un tipo de autorización ". A continuación, muestra que al pasar un dominio, las credenciales se utilizarán para los recursos * all * de ese dominio. – JaredReisinger

1

La solución es activar este parámetro en Apache:

BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On 


Más información: http://httpd.apache.org/docs/2.0/mod/mod_auth_digest.html#msie

A continuación, añadir esta propiedad en su código para el objeto WebRequest:

request.UserAgent = "MSIE" 

funciona muy bien para mí

+0

Sí, vea mi propio comentario sobre la pregunta original del 21 de julio de 2010. Es solo una opción cuando tiene control sobre el servidor y me molesta un poco que mi aplicación se identifique como MSIE;) – Cygon

2

Código tomado de este post ha funcionado perfectamente para mí Implement Digest authentication via HttpWebRequest in C#

había problema siguiente, cuando cada vez que el navegador la URL del feed en un navegador que pidió nombre de usuario y contraseña y Funcionó bien, sin embargo, ninguno de los ejemplos de código anteriores no funcionaba, al inspeccionar el Encabezado de solicitud/respuesta (en las herramientas de desarrollador web en Firefox) pude ver el encabezado con Autorización de tipo Digerir.

Paso 1 Añadir:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Security.Cryptography; 
using System.Text.RegularExpressions; 
using System.Net; 
using System.IO; 

namespace NUI 
{ 
    public class DigestAuthFixer 
    { 
     private static string _host; 
     private static string _user; 
     private static string _password; 
     private static string _realm; 
     private static string _nonce; 
     private static string _qop; 
     private static string _cnonce; 
     private static DateTime _cnonceDate; 
     private static int _nc; 

    public DigestAuthFixer(string host, string user, string password) 
    { 
     // TODO: Complete member initialization 
     _host = host; 
     _user = user; 
     _password = password; 
    } 

    private string CalculateMd5Hash(
     string input) 
    { 
     var inputBytes = Encoding.ASCII.GetBytes(input); 
     var hash = MD5.Create().ComputeHash(inputBytes); 
     var sb = new StringBuilder(); 
     foreach (var b in hash) 
      sb.Append(b.ToString("x2")); 
     return sb.ToString(); 
    } 

    private string GrabHeaderVar(
     string varName, 
     string header) 
    { 
     var regHeader = new Regex(string.Format(@"{0}=""([^""]*)""", varName)); 
     var matchHeader = regHeader.Match(header); 
     if (matchHeader.Success) 
      return matchHeader.Groups[1].Value; 
     throw new ApplicationException(string.Format("Header {0} not found", varName)); 
    } 

    private string GetDigestHeader(
     string dir) 
    { 
     _nc = _nc + 1; 

     var ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", _user, _realm, _password)); 
     var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", "GET", dir)); 
     var digestResponse = 
      CalculateMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, _nonce, _nc, _cnonce, _qop, ha2)); 

     return string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", " + 
      "algorithm=MD5, response=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\"", 
      _user, _realm, _nonce, dir, digestResponse, _qop, _nc, _cnonce); 
    } 

    public string GrabResponse(
     string dir) 
    { 
     var url = _host + dir; 
     var uri = new Uri(url); 

     var request = (HttpWebRequest)WebRequest.Create(uri); 

     // If we've got a recent Auth header, re-use it! 
     if (!string.IsNullOrEmpty(_cnonce) && 
      DateTime.Now.Subtract(_cnonceDate).TotalHours < 1.0) 
     { 
      request.Headers.Add("Authorization", GetDigestHeader(dir)); 
     } 

     HttpWebResponse response; 
     try 
     { 
      response = (HttpWebResponse)request.GetResponse(); 
     } 
     catch (WebException ex) 
     { 
      // Try to fix a 401 exception by adding a Authorization header 
      if (ex.Response == null || ((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized) 
       throw; 

      var wwwAuthenticateHeader = ex.Response.Headers["WWW-Authenticate"]; 
      _realm = GrabHeaderVar("realm", wwwAuthenticateHeader); 
      _nonce = GrabHeaderVar("nonce", wwwAuthenticateHeader); 
      _qop = GrabHeaderVar("qop", wwwAuthenticateHeader); 

      _nc = 0; 
      _cnonce = new Random().Next(123400, 9999999).ToString(); 
      _cnonceDate = DateTime.Now; 

      var request2 = (HttpWebRequest)WebRequest.Create(uri); 
      request2.Headers.Add("Authorization", GetDigestHeader(dir)); 
      response = (HttpWebResponse)request2.GetResponse(); 
     } 
     var reader = new StreamReader(response.GetResponseStream()); 
     return reader.ReadToEnd(); 
    } 
} 

}

Paso 2: Call nuevo método

DigestAuthFixer digest = new DigestAuthFixer(domain, username, password); 
string strReturn = digest.GrabResponse(dir); 

si URL es: http://xyz.rss.com/folder/rss entonces dominio: http://xyz.rss.com (parte de dominio) dir :/folder/rss (resto de la url)

también podría devolverlo como una secuencia y usar el método XmlDocument Load().

+0

Gran artículo. Tengo una pregunta que me sale var wwwAuthenticateHeader = ex.Response.Headers ["WWW-Authenticate"]; como nulo, ¿cuál puede ser el motivo? –

Cuestiones relacionadas