2010-06-24 14 views
6

ich an einen Web-Service von .NET bin verbinden, wie:HttpWebRequests sendet parameterlos URI in Authorization-Header

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

Der Autorisierungsheader wie folgt aussieht:

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

gibt der Server (400) Ungültige Anforderung. Ein Header senden von Chrome oder Internet Explorer wie folgt aussieht:

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

Wir vermuten, dass der Unterschied in der URI den Webdienst verursacht die Anforderung mit einem 400-Fehler zu leugnen. Ist es möglich, HttpRequest senden einen Autorisierungsheader, der die vollständige URI enthält?

+0

Welchen URI verwenden Sie zum Erstellen der Webanforderung? Enthält es den Teil "query? Id = 1"? – feroze

+0

Können Sie auch eine Wireshark-Trace einer erfolgreichen Anfrage vom Browser erhalten? Und dann vergleiche die beiden. Ich vermute, dass es vielleicht nichts mit dem Auth-Header zu tun hat. Wenn der Auth-Header nicht korrekt war, hätten Sie eine 401-Antwort (nicht 400) erhalten. – feroze

+0

@feroze: Beide Header in den Fragen stammen von Wireshark. Wenn dieser bestimmte Server denkt, dass der URI in dem Autorisierungsheader falsch ist, gibt es 400 statt 401 zurück – Andomar

Antwort

10

Es stellt sich heraus, dass Digest authentication ziemlich einfach zu implementieren ist. Mit unserer eigenen Implementierung konnten wir den vollständigen URI (einschließlich der Parameter) verwenden, um den MD5-Hash zu generieren. Das hat das Problem behoben.

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

Der Code für die DigestAuthFixer Klasse:

Falls jemand dieses Problem in Zukunft trifft, können Sie die Abhilfe, wie nennen kürzlich

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 - das klingt wie ein Fehler. Ich habe ein Problem auf der Microsoft Connect-Website geöffnet. Fühlen Sie sich frei, sich anzumelden und fügen Sie weitere Details in Bezug auf die Uri und Betriebssystem, .net Framework-Version etc. Hier ist das Problem verbinden Uri: https://connect.microsoft.com/VisualStudio/feedback/details/571052/digest -authentifizierung-does-not-send-the-voll-uri-pfad-im-uri-parameter – feroze

+0

danke.Das war sehr hilfreich. – kitwalker

+0

Diese Antwort kann verbessert werden, indem sie threadsicher gemacht wird (anstatt den freigegebenen 'statischen' Status zu verwenden),' IDisposable' Objekte korrekt entsorgt werden und andere FxCop-Compliance Fixes. – Dai

0

Es sieht aus wie Sie dieses Update installieren müssen, können Sie helfen:

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

Ihr Problem wahrscheinlich geschah, weil Sie nicht in der Lage waren, auf false die Keep-Alive-Eigenschaft festlegen, wenn Sie verwenden der HTTP-Adapter zum Veröffentlichen einer Nachricht

Stellen Sie außerdem sicher, dass PreAuthenticate auf True festgelegt ist.

+0

Das sieht wie einem Update für BizTalk aus. Wir verwenden nicht BizTalk, und ich kann KeepAlive und PreAuthenticate auf True setzen: das gleiche Ergebnis – Andomar

5

ich in dieser Ausgabe lief. Ich konnte den Workaround von Andomar nicht ohne kleine Anpassungen schaffen. Ich legte die Änderungen als Vorschlag für Andomars Antwort vor, aber sie wurden kurzerhand von TheTinMan und Luzifer zurückgewiesen. Da es mich und einige Kollegen Stunden gekostet hat, diese herauszufinden, und ich bin mir sicher, dass jemand anderes diesen Code benötigt, poste ich den Code als Antwort, um ihn verfügbar zu machen.

Hier ist der angepasste Code. Im Grunde wurde eine "undurchsichtige" Header-Variable benötigt, und einige Zitate mussten in GetDigestHeader repariert werden.

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

Ich stimme zu. Ihre Korrekturen waren absolut notwendig. – JoeMjr2

+0

Jedenfalls weißt du warum ich das WWW-Authenticate "als null" bekomme? –

Verwandte Themen