Как получить HttpClient для передачи учетных данных вместе с запросом?
у меня есть веб-приложение (размещенное в IIS), которое разговаривает со службой Windows. Служба Windows использует ASP.Net MVC Web API (self-hosted), и поэтому может быть передан через http с помощью JSON. Веб-приложение настроено для выполнения олицетворения, идея заключается в том, что пользователь, который делает запрос к веб-приложению, должен быть пользователем, который веб-приложение использует для выполнения запроса к службе. Структура выглядит так это:

(пользователь, выделенный красным цветом, является пользователем, на которого ссылаются в приведенных ниже примерах.)
веб-приложение делает запросы к службе Windows с помощью HttpClient:
var httpClient = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
httpClient.GetStringAsync("http://localhost/some/endpoint/");
это делает запрос к службе Windows, но не передает учетные данные правильно (служба сообщает пользователю как IIS APPPOOLASP.NET 4.0). это не то, что я хочу случись.
если я изменить приведенный выше код, чтобы использовать WebClient вместо этого учетные данные пользователя передаются правильно:
WebClient c = new WebClient
{
UseDefaultCredentials = true
};
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));
С помощью приведенного выше кода служба сообщает о пользователе как о пользователе, который сделал запрос к веб-приложению.
что я делаю не так с HttpClient реализация, которая заставляет его не передавать учетные данные правильно (или это ошибка с HttpClient)?
причина, по которой я хочу использовать HttpClient это то, что он имеет асинхронный API, который хорошо работает с Task s, тогда как WebClient'API-интерфейс с асинхронная нужно справиться с событиями.
7 ответов:
у меня тоже была такая же проблема. Я разработал Синхронное решение благодаря исследованию, проведенному @tpeczek в следующей статье SO:не удается выполнить проверку подлинности ASP.NET служба Web Api с HttpClient
мое решение использует
WebClient, который, как вы правильно отметили передает учетные данные без проблем. ПричинаHttpClientне работает из-за безопасности Windows, отключающей возможность создавать новые потоки под олицетворенной учетной записью (см. Так статья выше.)HttpClientсоздает новые потоки через Фабрику задач, тем самым вызывая ошибку.WebClientС другой стороны, выполняется синхронно в том же потоке, тем самым обходя правило и пересылая его учетные данные.хотя код работает, недостатком является то, что он не будет работать асинхронно.
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(); }Примечание: требуется пакет NuGet: Newtonsoft.Json, который является тем же самым сериализатором JSON, который использует WebAPI.
Вы можете настроить
HttpClientдля автоматической передачи учетных данных следующим образом:myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
то, что вы пытаетесь сделать, это заставить NTLM перенаправить идентификатор на следующий сервер, что он не может сделать - он может только выполнять олицетворение, которое дает вам доступ только к локальным ресурсам. Это не позволит вам пересечь границу машины. Проверка подлинности Kerberos поддерживает делегирование (то, что вам нужно) с помощью билетов, и билет может быть передан, когда все серверы и приложения в цепочке правильно настроены и Kerberos правильно настроен в домене. Итак, короче вам нужно переключитесь с использования NTLM на Kerberos.
для получения дополнительной информации о доступных вам параметрах проверки подлинности Windows и их работе начните с: http://msdn.microsoft.com/en-us/library/ff647076.aspx
хорошо, так что спасибо всем участникам выше. Я использую .NET 4.6, и у нас также была та же проблема. Я потратил время на отладку System.Net. Http, в частности HttpClientHandler, и нашел следующее:
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); }Итак, после оценки этого ExecutionContext.IsFlowSuppressed () возможно, был виновником, я завернул наш код олицетворения следующим образом:
using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate()) using (System.Threading.ExecutionContext.SuppressFlow()) { // HttpClient code goes here! }код внутри SafeCaptureIdenity (не моя орфографическая ошибка), захватывает Оконные проемы.Текущий () который является нашей олицетворенной идентичностью. Это подхватывается, потому что мы сейчас подавляем поток. Из-за использования/dispose это сбрасывается после вызова.
теперь это, кажется, работает для нас, фу!
хорошо, поэтому я взял код Joshoun и сделал его общим. Я не уверен, должен ли я реализовать шаблон singleton в классе SynchronousPost. Может быть, кто-то более осведомленный может помочь.
реализация
//Я предполагаю, что у вас есть свой конкретный тип. В моем случае я использую код сначала с классом под названием FileCategoryFileCategory x = new FileCategory { CategoryName = "Some Bs"}; SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>(); test.PostEntity(x, "/api/ApiFileCategories");общий класс здесь. Вы можете передать любой тип
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; } }мои классы Api выглядят так, если вы любопытно
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(); } }Я использую ninject, и РЕПО шаблон с единицей работы. В любом случае, общий класс выше действительно помогает.
это сработало для меня после того, как я настроил пользователя с доступом в интернет в службе Windows.
в моем коде:
HttpClientHandler handler = new HttpClientHandler(); handler.Proxy = System.Net.WebRequest.DefaultWebProxy; handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials; ..... HttpClient httpClient = new HttpClient(handler) ....
в .NET Core мне удалось получить
System.Net.Http.HttpClientСUseDefaultCredentials = trueдля передачи учетных данных Windows аутентифицированного пользователя в серверную службу с помощью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); }); }
Comments