2010-06-24 11 views
6

estoy conectando a un servicio web desde .NET, como:HttpWebRequests envía sin parámetros URI en la cabecera de Autorización

var request = (HttpWebRequest) WebRequest.Create(uri); 
request.Credentials = new NetworkCredential("usr", "pwd", "domain"); 
var response = (HttpWebResponse) request.GetResponse(); 

La cabecera de autorización se parece a:

Authorization: Digest username="usr",realm="domain",nonce="...", 
    uri="/dir",algorithm="MD5",etc... 
    ^^^^^^^^^^ 

el servidor devuelve (400) Solicitud incorrecta. Un encabezado enviar por Chrome o IE se ve así:

Authorization: Digest username="usr", realm="domain", nonce="...", 
    uri="/dir/query?id=1", algorithm=MD5, etc... 
    ^^^^^^^^^^^^^^^^^^^^^ 

Sospechamos que la diferencia en la URI está causando el servicio web para rechazar la solicitud con un error 400. ¿Es posible hacer que HttpRequest envíe un encabezado de autorización que incluya el URI completo?

+0

¿Qué URI está utilizando para crear la solicitud web? ¿Contiene la parte "query? Id = 1"? – feroze

+0

Además, ¿puede obtener un rastro de wireshark de una solicitud exitosa del navegador? Y luego compara los dos. Sospecho que podría no tener nada que ver con el encabezado de autenticación. Si el encabezado de autenticación no era correcto, habría recibido una respuesta 401 (no 400) – feroze

+0

@feroze: los dos encabezados en las preguntas son de Wireshark. Si este servidor en particular cree que el URI en el encabezado de autorización es incorrecto, devuelve 400 en lugar de 401 – Andomar

Respuesta

10

Resulta que Digest authentication es bastante fácil de implementar. Con nuestra propia implementación, pudimos usar el URI completo (incluidos los parámetros) para generar el hash MD5. Eso solucionó el problema.

En caso de que alguien llega a este problema en el futuro, puede llamar a la solución como:

var resultText = DigestAuthFixer.GrabResponse("/dir/index.html"); 

El código para la clase DigestAuthFixer:

public static class DigestAuthFixer 
{ 
    private static string _host = "http://localhost"; 
    private static string _user = "Mufasa"; 
    private static string _password = "Circle Of Life"; 
    private static string _realm; 
    private static string _nonce; 
    private static string _qop; 
    private static string _cnonce; 
    private static DateTime _cnonceDate; 
    private static int _nc; 

    private static 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 static 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)); 
    } 

    // http://en.wikipedia.org/wiki/Digest_access_authentication 
    private static 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 static 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(); 
    } 
} 
+0

Hmm - esto suena como un error. Abrí un problema en el sitio web de Microsoft Connect. Siéntase libre de iniciar sesión y agregar más detalles sobre el URI, y el sistema operativo, la versión de .NET Framework, etc. aquí está el problema de conexión Uri: https://connect.microsoft.com/VisualStudio/feedback/details/571052/digest -authentication-does-not-send-the-full-uri-path-in-the-uri-parameter – feroze

+0

gracias.esto fue extremadamente útil. – kitwalker

+0

Esta respuesta se puede mejorar haciendo que sea segura para subprocesos (en lugar de usar el estado 'estático 'compartido), descartando objetos' IDisposable' correctamente y otras correcciones FxCop. – Dai

0

Parece que es necesario instalar esta revisión podría ayudarle a cabo:

http://support.microsoft.com/?kbid=924638

Su problema fue probablemente pasando porque no fueron capaces de establecer la propiedad KeepAlive en false cuando se está utilizando el adaptador HTTP para publicar un mensaje

También asegúrese de que PreAuthenticate está establecido en verdadero.

+0

Eso parece una solución para BizTalk. No estamos usando BizTalk, y puedo establecer KeepAlive y PreAuthenticate en true: el mismo resultado – Andomar

5

me encontré con este problema recientemente. No pude obtener la solución de Andomar para trabajar sin algunos ajustes menores. Presenté los cambios como una sugerencia a la respuesta de Andomar, pero TheTinMan y Lucifer los rechazaron sin ceremonias. Dado que me tomó horas y algunos colegas calcular esto y estoy seguro de que alguien más necesitará esto, estoy publicando el código como una respuesta para que esté disponible.

Aquí está el código ajustado. Básicamente, se necesitaba una variable de encabezado "opaca", y algunas citas necesitaban ser corregidas en GetDigestHeader.

public static class DigestAuthFixer 
{ 
    private static string _host = "http://localhost"; 
    private static string _user = "Mufasa"; 
    private static string _password = "Circle Of Life"; 
    private static string _realm; 
    private static string _nonce; 
    private static string _qop; 
    private static string _cnonce; 
    private static string _opaque; 
    private static DateTime _cnonceDate; 
    private static int _nc = 0; 

    private static 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 static 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)); 
    } 

    // http://en.wikipedia.org/wiki/Digest_access_authentication 
    private static 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}\", opaque=\"{8}\"", 
     _user, _realm, _nonce, dir, digestResponse, _qop, _nc, _cnonce, _opaque); 
    } 

    public static 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); 
      _opaque = GrabHeaderVar("opaque", 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(); 
    } 
} 
+0

Estoy de acuerdo. Tus correcciones fueron absolutamente necesarias. – JoeMjr2

+0

¿Alguna vez sabes por qué obtengo el WWW-Authenticate "como nulo? –

Cuestiones relacionadas