2010-07-03 19 views
17

Różne artykuły (1, 2) odkryłem zrobić to wyglądać dość łatwe:Jak mogę wykonać uwierzytelnianie szyfrowane za pomocą 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; 

Jednak to działa tylko dla adresów URL bez parametrów URL. Na przykład, można pobrać http://example.com/test/xyz.html dobrze, ale gdy próbuję pobrać http://example.com/test?page=xyz, wynik to 400 Zła wiadomość Zapytanie z następujących logów serwera (Apache 2.2):

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

Moim pierwszym pomysłem było że specyfikacja digest wymaga usunięcia parametrów adresu URL z skrótu digest, ale usunięcie parametru z adresu URL przekazanego do credentialCache.Add() niczego nie zmieniło. Musi być odwrotnie, a gdzieś w środowisku .NET błędnie usuwa parametr z adresu URL.

+0

Oto podobne pytanie na stronie SO moje początkowe wyszukiwanie nie wymyślił: http://stackoverflow.com/questions/3109507/httpwebrequests-sends-parameterless-uri-in-authorization-header – Cygon

+0

I Microsoft Connect raport o błędzie: 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 Zgłoszony powyżej raport o błędzie wydaje się mieć obejście, opublikowane 6/26. Czy próbowałeś tego? –

Odpowiedz

0

Myślę, że drugi adres URL wskazuje na stronę dynamiczną i powinieneś najpierw wywołać ją za pomocą polecenia GET, aby pobrać kod HTML, a następnie go pobrać. Brak doświadczenia w tej dziedzinie.

+0

Przepraszam, nie. Całkowicie zależy od serwera internetowego, co zrobić z adresem URL, a pierwsza strona może być dynamiczna. Co więcej, HTML jest pobrany, nie ma różnicy między pobieraniem HTML a pobieraniem czegoś innego. – Cygon

4

Powiedziałeś, że usunąłeś parametry zapytania, ale czy próbowałeś wrócić do hosta? Każdy przykład CredentialsCache.Add(), jaki widziałem, wydaje się używać tylko hosta, a dokumenty dla CredentialsCache.Add() wymieniają parametr Uri jako "uriPrefix", co wydaje się być wymowne.

Innymi słowy, to wypróbować:

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; 

Jeśli to zadziała, będzie trzeba także upewnić się, że nie należy dodawać tę samą „Urząd” do pamięci podręcznej więcej niż raz ... wszystkie żądania do tego samego hosta powinny być w stanie korzystać z tego samego wpisu pamięci podręcznej poświadczeń.

+0

Dziwne, nie natknąłem się na jeden przykład, używając tylko głównego URI do uwierzytelnienia. W każdym razie to nie działa, przepraszam. Zgodnie z sekcją 3.2.2 dokumentu RFC 2617 (http://rfc.askapache.com/rfc2617/rfc2617.html#section-3.2.2) identyfikator URI skrótu powinien być identyczny z "request-uri" w żądaniu HTTP. – Cygon

+0

Oto kilka przykładów: 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 (choć wprawdzie jest to przykład "localhost"). – JaredReisinger

+0

Tak, RFC mówi, że digest-uri powinno pasować do żądania, ale to jest to, co jest wysyłane na drucie, a nie to, co jest przechowywane w pamięci podręcznej. Dokument CredentialCache.GetCredential() doc (http://msdn.microsoft.com/en-us/library/fy4394xd.aspx) mówi, że "GetCredential używa najdłuższego zgodnego prefiksu URI w pamięci podręcznej, aby określić, który zestaw poświadczeń zwrócić za typ autoryzacji. " Następnie pokazuje, że przekazanie domeny spowoduje, że poświadczenia zostaną wykorzystane do * wszystkich * zasobów w tej domenie. – JaredReisinger

1

Rozwiązaniem jest, aby włączyć ten parametr w apache:

BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On 


Więcej informacji: http://httpd.apache.org/docs/2.0/mod/mod_auth_digest.html#msie

Następnie dodać tę właściwość w kodzie dla WebRequest obiektu:

request.UserAgent = "MSIE" 

to działa bardzo dobrze dla mnie

+0

Tak, zobacz mój własny komentarz do oryginalnego pytania z 21 lipca 2010. Jest to tylko opcja, gdy masz kontrolę nad serwerem i trochę mnie to irytuje, że moja aplikacja musi się identyfikować jako MSIE;) – Cygon

2

Kod wzięty z tym stanowisku pracował idealnie dla mnie Implement Digest authentication via HttpWebRequest in C#

Miałem następujący problem, gdy kiedykolwiek przeglądarka url RSS w przeglądarce to poproszony o nazwę użytkownika i hasło pracował dobrze, jednak jakikolwiek z powyższych przykładów kodu nie działał, podczas sprawdzania nagłówka żądania/odpowiedzi (w narzędziach programistycznych w przeglądarce Firefox) mogłem zobaczyć nagłówek z autoryzacją typu digest.

Etap 1 dodawania:

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

}

Etap 2: połączenie Nowa metoda

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

jeśli URL to http://xyz.rss.com/folder/rss następnie domain http://xyz.rss.com (część domeny) dir :/folder/rss (reszta adresu URL)

można również zwrócić jako strumień i użyć metody Load() XmlDocument.

+0

Świetny artykuł. Mam jedno pytanie, które dostaję var wwwAuthenticateHeader = ex.Response.Headers ["WWW-Authenticate"]; jako null, jaki może być powód? –

Powiązane problemy