2010-06-24 5 views
6

Je me connecte à un service Web à partir de .NET, comme:HttpWebRequests envoie URI parameterless en-tête d'autorisation

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

L'en-tête d'autorisation ressemble:

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

Le serveur renvoie (400) Mauvaise Demande. Un en-tête envoyer par Chrome ou IE ressemble à:

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

Nous soupçonnons que la différence URI est à l'origine du service Web pour refuser la demande avec une erreur 400. Est-il possible de faire en sorte que HttpRequest envoie un en-tête Authorization qui inclut l'URI complet?

+0

Quelle adresse URI utilisez-vous pour créer la requête Web? Contient-il la partie "query? Id = 1"? – feroze

+0

En outre, pouvez-vous obtenir une trace de wireshark d'une demande réussie du navigateur? Et puis comparez les deux. Je soupçonne que cela n'a rien à voir avec l'en-tête auth. Si l'en-tête auth n'était pas correct, vous auriez reçu une réponse 401 (pas 400) – feroze

+0

@feroze: Les deux en-têtes des questions proviennent de Wireshark. Si ce serveur particulier pense que l'URI dans l'en-tête d'autorisation est incorrect, il renvoie 400 au lieu de 401 – Andomar

Répondre

10

Il s'avère que Digest authentication est assez facile à implémenter. Avec notre propre implémentation, nous avons pu utiliser l'URI complet (y compris les paramètres) pour générer le hachage MD5. Cela a réglé le problème.

Dans le cas où quelqu'un frappe ce problème à l'avenir, vous pouvez appeler la solution de contournement comme:

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

Le code pour la classe 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 - cela ressemble à un bogue. J'ai ouvert un problème sur le site Web Microsoft Connect. N'hésitez pas à vous connecter et à ajouter plus de détails concernant l'Uri, et le système d'exploitation, la version du framework .net, etc. voici le problème de connexion Uri: https://connect.microsoft.com/VisualStudio/feedback/details/571052/digest -authentication-ne-envoie-pas-le-plein-uri-chemin-dans-le-uri-paramètre – feroze

+0

merci.C'était extrêmement utile. – kitwalker

+0

Cette réponse peut être améliorée en la rendant sûre pour les threads (au lieu d'utiliser l'état statique 'static'), en éliminant correctement les objets 'IDisposable' et en corrigeant les autres conformités FxCop. – Dai

0

Il semble que vous devez installer ce correctif peut vous aider:

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

Votre problème se passait probablement parce que vous étiez pas en mesure de définir la propriété KeepAlive false lorsque vous utilisez l'adaptateur HTTP pour publier un message

Vérifiez également que PreAuthenticate est défini sur true.

+0

Cela ressemble à un correctif pour BizTalk. Nous n'utilisons pas BizTalk et je peux définir KeepAlive et PreAuthenticate sur true: même résultat – Andomar

5

je suis tombé sur cette question récemment. Je ne pouvais pas obtenir la solution de contournement d'Andomar pour travailler sans quelques ajustements mineurs. J'ai soumis les changements comme une suggestion à la réponse d'Andomar, mais ils ont été rejetés sans ménagement par TheTinMan et Lucifer. Comme il m'a fallu des heures et des collègues pour les comprendre et je suis sûr que quelqu'un d'autre en aura besoin, je poste le code comme réponse pour le rendre disponible.

Voici le code ajusté. Fondamentalement, une variable d'en-tête "opaque" était nécessaire, et certaines citations devaient être corrigées dans 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

Je suis d'accord. Vos corrections étaient absolument nécessaires. – JoeMjr2

+0

N'importe quel cas vous savez pourquoi je reçois le WWW-Authenticate "comme null? –