2010-07-03 3 views
17

Divers articles (1, 2) Je découvrais ce look assez facile:Comment puis-je faire une authentification Digest avec 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; 

Cependant, cela ne fonctionne que pour les URL sans paramètres d'URL. Par exemple, je peux télécharger http://example.com/test/xyz.html très bien, mais lorsque je tente de télécharger http://example.com/test?page=xyz, le résultat est un 400 Bad message de demande qui suit dans les journaux de serveur (Apache 2.2 en cours d'exécution):

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

Ma première idée était que la spécification digest nécessite que les paramètres d'URL soient supprimés du hachage digest - mais la suppression du paramètre de l'URL transmise à credentialCache.Add() n'a rien changé. Donc, il doit être l'inverse et quelque part dans le framework .NET est de supprimer à tort le paramètre de l'URL.

+0

Voici une question similaire sur SO ma recherche initiale n'est pas venu avec: http://stackoverflow.com/questions/3109507/httpwebrequests-sends-parameterless-uri-in-authorization-header – Cygon

+0

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

+0

Le Microsoft Connect rapport de bug lié ci-dessus semble avoir une solution de contournement, posté 6/26. Avez-vous essayé cela? –

Répondre

0

Je pense que la deuxième URL pointe vers une page dynamique et vous devez d'abord l'appeler en utilisant GET pour obtenir le code HTML, puis le télécharger. Aucune expérience dans ce domaine cependant.

+0

Non désolé. C'est au serveur Web de décider quoi faire avec l'URL et la première page pourrait aussi être dynamique. En outre, le HTML est ce qui est téléchargé, il n'y a aucune différence entre le téléchargement de HTML ou le téléchargement d'autre chose. – Cygon

4

Vous avez dit que vous avez supprimé les paramètres de la chaîne de requête, mais avez-vous essayé de revenir uniquement à l'hôte? Chaque exemple de CredentialsCache.Add() que j'ai vu semble n'utiliser que l'hôte, et les docs pour CredentialsCache.Add() listent le paramètre Uri comme "uriPrefix", ce qui semble révélateur.

En d'autres termes, essayez ceci:

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 cela fonctionne, vous devrez également vous assurer que vous n'ajoutez pas la même « autorité » dans le cache plus d'une fois ... Toutes les demandes adressées au même hôte doivent pouvoir utiliser la même entrée de cache des informations d'identification.

+0

Étrange, je n'ai pas rencontré un seul exemple en utilisant seulement l'URI racine pour l'authentification. En tout cas, ça ne marche pas, désolé. Conformément à la section 3.2.2 de la RFC 2617 (http://rfc.askapache.com/rfc2617/rfc2617.html#section-3.2.2), l'adresse URI du résumé devrait être identique à l'URI de la demande dans la requête HTTP. – Cygon

+0

Voici quelques exemples: 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 (bien que ce soit un exemple 'localhost'). – JaredReisinger

+0

Oui, le RFC dit que le digest-uri devrait correspondre à la requête, mais c'est ce qui est envoyé sur le réseau, pas ce qui est stocké dans le cache. Le document CredentialCache.GetCredential() doc (http://msdn.microsoft.com/en-us/library/fy4394xd.aspx) indique que "GetCredential utilise le préfixe URI correspondant le plus long dans le cache pour déterminer quel jeu d'informations d'identification à renvoyer pour un type d'autorisation. " Il montre ensuite que le passage d'un domaine entraînera l'utilisation des informations d'identification pour les ressources * all * sous ce domaine. – JaredReisinger

1

La solution est d'activer ce paramètre dans apache:

BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On 


Plus d'infos: http://httpd.apache.org/docs/2.0/mod/mod_auth_digest.html#msie

Puis ajouter cette propriété dans votre code pour l'objet WebRequest:

request.UserAgent = "MSIE" 

ça marche très bien pour moi

+0

Oui, voir mon propre commentaire sur la question originale du 21 juillet 2010. C'est seulement une option quand vous avez le contrôle sur le serveur et cela me gêne un peu que mon application doit s'identifier comme MSIE, cependant;) – Cygon

2

code tiré de ce poste a parfaitement fonctionné pour moi Implement Digest authentication via HttpWebRequest in C#

j'avais question suivante, chaque fois que je navigateur l'URL du flux dans un navigateur, il a demandé le nom d'utilisateur et mot de passe a bien fonctionné, cependant l'un des exemples de code ci-dessus ne fonctionnait pas, en inspectant l'en-tête de demande/réponse (dans les outils de développement web dans Firefox) je pouvais voir l'en-tête ayant l'autorisation de digérer type.

Étape 1 Ajouter:

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

}

Etape 2: Appel nouvelle méthode

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

si l'URL est: http://xyz.rss.com/folder/rss alors domaine: http://xyz.rss.com (partie de domaine) dir :/folder/rss (reste de l'URL)

vous pouvez également le renvoyer en tant que flux et utiliser la méthode XmlDocument Load().

+0

Grand article. J'ai une question que j'obtiens var wwwAuthenticateHeader = ex.Response.Headers ["WWW-Authenticate"]; comme nulle, quelle peut être la raison? –

Questions connexes