Как получить 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-интерфейс с асинхронная нужно справиться с событиями.

813   7  

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. Может быть, кто-то более осведомленный может помочь.

реализация

//Я предполагаю, что у вас есть свой конкретный тип. В моем случае я использую код сначала с классом под названием FileCategory
FileCategory 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

    Ничего не найдено.