Como obter o HttpClient para passar as credenciais junto com a solicitação?

Eu tenho um aplicativo da web (hospedado no IIS) que fala com um serviço do Windows. O serviço do Windows está usando a API da Web do ASP.Net MVC (auto-hospedado) e, portanto, pode ser comunicado com o http usando JSON. O aplicativo da Web é configurado para fazer representação, a idéia é que o usuário que faz a solicitação para o aplicativo da Web deve ser o usuário que o aplicativo da Web usa para fazer a solicitação para o serviço. A estrutura é assim:

(O usuário destacado em vermelho é o usuário mencionado nos exemplos abaixo.)


O aplicativo da Web faz solicitações para o serviço do Windows usando um HttpClient :

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

Isso faz a solicitação para o serviço do Windows, mas não passa as credenciais corretamente (o serviço informa o usuário como IIS APPPOOL\ASP.NET 4.0 ). Não é isso que quero que aconteça .

Se eu alterar o código acima para usar um WebClient , as credenciais do usuário serão passadas corretamente:

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

Com o código acima, o serviço informa o usuário como o usuário que fez a solicitação para o aplicativo da web.

O que estou fazendo de errado com a implementação HttpClient que está fazendo com que ele não passe as credenciais corretamente (ou é um bug com o HttpClient )?

O motivo pelo qual eu quero usar o HttpClient é que ele tem uma API assíncrona que funciona bem com as Task , enquanto a API asyc do WebClient precisa ser manipulada com events.

Eu também estava tendo esse mesmo problema. Eu desenvolvi uma solução síncrona graças à pesquisa feita pelo @tpeczek no seguinte artigo SO: Não é possível autenticar para o serviço ASP.NET Web Api com HttpClient

Minha solução usa um WebClient , que, como você observou corretamente, passa as credenciais sem problemas. O motivo pelo qual o HttpClient não funciona é porque a segurança do Windows desativa a capacidade de criar novos encadeamentos sob uma conta representada (consulte o artigo sobre SO acima). O HttpClient cria novos encadeamentos por meio do Task Factory, causando o erro. WebClient por outro lado, é executado de forma síncrona no mesmo encadeamento, ignorando a regra e encaminhando suas credenciais.

Embora o código funcione, a desvantagem é que ele não funcionará como asynchronous.

 var wi = (System.Security.Principal.WindowsIdentity)HttpContext.Current.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(); } 

Nota: Requer o pacote NuGet: Newtonsoft.Json, que é o mesmo usado pelo serializador JSON que o WebAPI usa.

Você pode configurar o HttpClient para passar automaticamente credenciais como esta:

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

O que você está tentando fazer é fazer com que o NTLM encaminhe a identidade para o próximo servidor, o que ele não pode fazer – só pode representar a representação, que só lhe dá access a resources locais. Não vai deixar você cruzar um limite de máquina. A autenticação Kerberos oferece suporte à delegação (o que você precisa) usando tickets e o ticket pode ser encaminhado quando todos os servidores e aplicativos da cadeia estiverem corretamente configurados e o Kerberos estiver configurado corretamente no domínio. Então, em suma, você precisa mudar de usar o NTLM para o Kerberos.

Para obter mais informações sobre as opções de Autenticação do Windows disponíveis para você e como elas funcionam, acesse: http://msdn.microsoft.com/pt-br/library/ff647076.aspx

OK, obrigado a todos os colaboradores acima. Estou usando o .NET 4.6 e também tivemos o mesmo problema. Passei tempo debugging System.Net.Http, especificamente o HttpClientHandler e encontrei o seguinte:

  if (ExecutionContext.IsFlowSuppressed()) { IWebProxy webProxy = (IWebProxy) null; if (this.useProxy) webProxy = this.proxy ?? WebRequest.DefaultWebProxy; if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null) this.SafeCaptureIdenity(state); } 

Então, depois de avaliar que o ExecutionContext.IsFlowSuppressed () pode ter sido o culpado, encapsulei nosso código de representação da seguinte forma:

 using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate()) using (System.Threading.ExecutionContext.SuppressFlow()) { // HttpClient code goes here! } 

O código dentro de SafeCaptureIdenity (não meu erro de ortografia), agarra WindowsIdentity.Current () que é nossa identidade representada. Isto está sendo pego porque agora estamos suprimindo o stream. Por causa do uso / descarte isso é redefinido após a chamada.

Agora parece funcionar para nós, ufa!

Ok, então peguei o código Joshoun e o tornei genérico. Não tenho certeza se devo implementar o padrão singleton na class SynchronousPost. Talvez alguém mais possa ajudar.

Implementação

// Eu suponho que você tenha seu próprio tipo de concreto. No meu caso eu estou usando o código primeiro com uma class chamada FileCategory

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

Classe genérica aqui. Você pode passar qualquer tipo

  public class SynchronousPostwhere 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; } } 

Minhas aulas de Api se parecem com isso, se você está curioso

 public class ApiFileCategoriesController : ApiBaseController { public ApiFileCategoriesController(IMshIntranetUnitOfWork unitOfWork) { UnitOfWork = unitOfWork; } public IEnumerable 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(); } } 

Eu estou usando ninject e repo padrão com unidade de trabalho. De qualquer forma, a class genérica acima realmente ajuda.

Funcionou para mim depois que eu configurei um usuário com access à Internet no serviço do Windows.

No meu código:

 HttpClientHandler handler = new HttpClientHandler(); handler.Proxy = System.Net.WebRequest.DefaultWebProxy; handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials; ..... HttpClient httpClient = new HttpClient(handler) .... 

No .NET Core, consegui obter um System.Net.Http.HttpClient com UseDefaultCredentials = true para passar as credenciais do Windows do usuário autenticado para um serviço de backend usando o WindowsIdentity.RunImpersonated .

 HttpClient client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true } ); HttpResponseMessage response = null; if (identity is WindowsIdentity windowsIdentity) { await WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, async () => { var request = new HttpRequestMessage(HttpMethod.Get, url) response = await client.SendAsync(request); }); }