207 votes

Comment obtenir que HttpClient transmette les informations d'identification avec la demande?

J'ai une application web (hébergé dans IIS) qui parle à un service Windows. Le service Windows à l'aide de la ASP.Net MVC, Web API (auto-hébergé), et peuvent donc être communiqués avec sur http en utilisant JSON. L'application web est configurée pour faire de l'usurpation d'identité, l'idée étant que l'utilisateur qui en fait la demande à l'application web doit être l'utilisateur que l'application web utilise pour faire la demande sur le service. La structure ressemble à ceci:

(L'utilisateur mis en évidence en rouge est l'utilisateur mentionné dans les exemples ci-dessous).


L'application web fait des demandes pour le service Windows à l'aide d'un HttpClient:

var httpClient = new HttpClient(new HttpClientHandler() 
                      {
                          UseDefaultCredentials = true
                      });
httpClient.GetStringAsync("http://localhost/some/endpoint/");

Cela en fait la demande au service de Windows, mais ne pas transmettre les informations d'identification correctement (les rapports du service à l'utilisateur en tant que IIS APPPOOL\ASP.NET 4.0). Ce n'est pas ce que je veux.

Si je change le code ci-dessus pour utiliser un WebClient au lieu de cela, les informations d'identification de l'utilisateur sont transmises correctement:

WebClient c = new WebClient
                   {
                       UseDefaultCredentials = true
                   };
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));

Avec le code ci-dessus, les rapports de service de l'utilisateur qui en fait la demande à l'application web.

Ce que je fais de mal avec l' HttpClient mise en œuvre qui est la cause de ne pas transmettre les informations d'identification correctement (ou est-ce un bug avec l' HttpClient)?

La raison pour laquelle je veux utiliser l' HttpClient , c'est qu'il a une API asynchrone qui fonctionne bien avec Tasks, alors que l' WebClients'asyc de l'API doit être manipulé avec les événements.

192voto

Sean Points 168

Vous pouvez configurer HttpClient pour transmettre automatiquement les informations d'identification comme ceci:

 myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
 

76voto

Joshua Points 2094

J'ai été aussi avoir ce même problème. J'ai développé une solution synchrone grâce aux recherches effectuées par @tpeczek dans les tâches suivantes pour l'article: Impossible de s'authentifier à ASP.NET l'Api Web service avec HttpClient

Ma solution utilise un WebClient, qui comme vous l'avez justement noté transmet les informations d'identification sans problème. La raison en HttpClient ne fonctionne pas est en raison de la sécurité de Windows désactiver la capacité de créer de nouveaux threads sous un compte avec emprunt d'identité (voir SI l'article ci-dessus). HttpClient crée de nouveaux threads via la Tâche de l'Usine ainsi l'origine de l'erreur. WebClient d'autre part, s'exécute de façon synchrone sur le même thread, contournant ainsi la règle et la transmission de ses informations d'identification.

Bien que le code fonctionne, l'inconvénient est que cela ne fonctionnera pas async.

var wi = (WindowsIdentity)HttpContext.User.Identity;

var wic = wi.Impersonate();
try
{
    var data = JsonConvert.SerializeObject(new
    {
        Property1 = 1,
        Property2 = "blah"
    });

    using (var client = new WebClient { UseDefaultCredentials = true })
    {
        client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
        client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
    }
}
catch (Exception exc)
{
    // handle exception
}
finally
{
    wic.Undo();
}

Note: Nécessite de package NuGet: Newtonsoft.Json, qui est la même sérialiseur JSON WebAPI utilise.

32voto

BlackSpy Points 2547

Ce que vous essayez de faire est d'obtenir NTLM à l'avant de l'identité sur le côté serveur, il ne peut pas le faire - il ne peut faire l'usurpation d'identité qui ne vous donne accès qu'à des ressources locales. Il ne vous laisse pas traverser une machine de limite. L'authentification Kerberos prend en charge la délégation (ce que vous avez besoin) à l'aide de billets, et le billet peut être transmis lorsque tous les serveurs et les applications de la chaîne sont correctement configurés et Kerberos est configuré correctement sur le domaine. Donc, en bref vous avez besoin pour passer de l'utilisation de NTLM pour Kerberos.

Pour en savoir plus sur l'Authentification Windows options disponibles pour vous et la façon dont ils travaillent début: http://msdn.microsoft.com/en-us/library/ff647076.aspx

3voto

jvelez Points 641

Ok, donc j'ai pris Joshoun code et fait générique. Je ne suis pas sûr si je devrais implémenter le pattern singleton sur SynchronousPost classe. Peut-être que quelqu'un de plus knowledgeble peut vous aider.

La mise en œuvre

//Je suppose que vous avez votre propre type de béton. Dans mon cas, je suis en utilisant le premier code avec une classe appelée FileCategory

FileCategory x = new FileCategory { CategoryName = "Some Bs"};
SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>();
test.PostEntity(x, "/api/ApiFileCategories"); 

Classe générique ici. Vous pouvez passer n'importe quel type

 public class SynchronousPost<T>where T :class
    {
        public SynchronousPost()
        {
            Client = new WebClient { UseDefaultCredentials = true };
        }

        public void PostEntity(T PostThis,string ApiControllerName)//The ApiController name should be "/api/MyName/"
        {
            //this just determines the root url. 
            Client.BaseAddress = string.Format(
         (
            System.Web.HttpContext.Current.Request.Url.Port != 80) ? "{0}://{1}:{2}" : "{0}://{1}",
            System.Web.HttpContext.Current.Request.Url.Scheme,
            System.Web.HttpContext.Current.Request.Url.Host,
            System.Web.HttpContext.Current.Request.Url.Port
           );
            Client.Headers.Add(HttpRequestHeader.ContentType, "application/json;charset=utf-8");
            Client.UploadData(
                                 ApiControllerName, "Post", 
                                 Encoding.UTF8.GetBytes
                                 (
                                    JsonConvert.SerializeObject(PostThis)
                                 )
                             );  
        }
        private WebClient Client  { get; set; }
    }

Mon Api classs ressemble à ça, si vous êtes curieux

public class ApiFileCategoriesController : ApiBaseController
{
    public ApiFileCategoriesController(IMshIntranetUnitOfWork unitOfWork)
    {
        UnitOfWork = unitOfWork;
    }

    public IEnumerable<FileCategory> GetFiles()
    {
        return UnitOfWork.FileCategories.GetAll().OrderBy(x=>x.CategoryName);
    }
    public FileCategory GetFile(int id)
    {
        return UnitOfWork.FileCategories.GetById(id);
    }
    //Post api/ApileFileCategories

    public HttpResponseMessage Post(FileCategory fileCategory)
    {
        UnitOfWork.FileCategories.Add(fileCategory);
        UnitOfWork.Commit(); 
        return new HttpResponseMessage();
    }
}

Je suis à l'aide de ninject, et les pensions de modèle de l'unité de travail. De toute façon, la classe générique ci-dessus aide vraiment.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X