HttpClient'i istekle birlikte kimlik bilgilerini nasıl alabilirim?


164

Bir Windows hizmetiyle konuşan bir web uygulamam (IIS'de barındırılıyor) var. Windows hizmeti ASP.Net MVC Web API'sini (kendi kendine barındırılan) kullanıyor ve bu nedenle http üzerinden JSON kullanılarak iletilebilir. Web uygulaması, kimliğe bürünme yapmak üzere yapılandırılmıştır; bu, web uygulamasına istekte bulunan kullanıcının, web uygulamasının hizmete istekte bulunmak için kullandığı kullanıcı olması gerektiği fikridir. Yapı şöyle görünür:

(Kırmızı ile vurgulanan kullanıcı, aşağıdaki örneklerde belirtilmiş olan kullanıcıdır.)


Web uygulaması, Windows hizmetinden aşağıdakileri kullanarak istekte bulunur HttpClient:

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

Bu, Windows hizmetine istekte bulunur, ancak kimlik bilgilerini doğru bir şekilde iletmez (hizmet kullanıcıyı bildirir IIS APPPOOL\ASP.NET 4.0). Olmak istediğim bu değil .

WebClientBunun yerine yukarıdaki kodu değiştirmek, kullanıcının kimlik bilgileri doğru geçirilir:

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

Yukarıdaki kodla, hizmet kullanıcıyı web uygulamasına istekte bulunan kullanıcı olarak bildirir.

HttpClientKimlik bilgilerini doğru bir şekilde geçirmemesine neden olan uygulamada ne yapıyorum (ya da ile ilgili bir hata HttpClientmıdır)?

Kullanmak istediğim nedeni HttpClient, Tasks ile iyi çalışan bir async API'sine sahipken, WebClient'syc API'sı olaylarla işlenmesi gerekiyor.



HttpClient ve WebClient, farklı şeyleri DefaultCredentials olarak kabul ediyor gibi görünüyor. HttpClient.setCredentials (...) uygulamasını denediniz mi?
Germann Arlington

BTW, WebClient'tır sahiptir DownloadStringTaskAsyncda zaman uyumsuz / bekliyoruz kullanılabilir .Net 4.5, içinde
LB

1
@GermannArlington: HttpClientbir SetCredentials()yöntemi yok. Beni ne demek istediğine işaret edebilir misin?
adrianbanks

4
Bu düzeltildi gibi görünüyor (.net 4.5.1)? new HttpClient(new HttpClientHandler() { AllowAutoRedirect = true, UseDefaultCredentials = true }Windows kimliği doğrulanmış bir kullanıcı tarafından erişilen bir web sunucusunda oluşturmayı denedim ve web sitesi bundan sonra başka bir uzak kaynak için kimlik doğrulaması yaptı (bayrak ayarlanmadan kimlik doğrulaması yapılmaz).
GSerg

Yanıtlar:


67

Ben de aynı sorunu yaşıyordum. Aşağıdaki SO makalesinde @ tpeczek tarafından yapılan araştırma sayesinde senkronize bir çözüm geliştirdim: HttpClient ile ASP.NET Web Api hizmetine kimlik doğrulaması yapılamıyor

Çözümüm WebClient, doğru şekilde belirttiğiniz gibi kimlik bilgilerini sorunsuz bir şekilde geçiren bir a kullanır . Bunun nedeni HttpClient, Windows güvenliğinin kimliğe bürünmüş bir hesap altında yeni iş parçacıkları oluşturma yeteneğini devre dışı bırakmasıdır (yukarıdaki SO makalesine bakın.) HttpClientGörev Fabrikası aracılığıyla yeni iş parçacıkları oluşturur ve bu da hataya neden olur. WebClientdiğer yandan, aynı iş parçacığı üzerinde eşzamanlı olarak çalışır, böylece kuralı atlar ve kimlik bilgilerini iletir.

Kod çalışmasına rağmen, olumsuz, zaman uyumsuz çalışmayacak olmasıdır.

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

Not: NuGet paketi gerektirir: WebAPI'nin kullandığı JSON serileştiricisi ile aynı olan Newtonsoft.Json.


1
Sonunda benzer bir şey yaptım ve gerçekten iyi çalışıyor. Aramaların engellenmesini istediğim gibi, eşzamansız sorun bir sorun değil.
adrianbanks

136

Bunun HttpClientgibi kimlik bilgilerini otomatik olarak iletecek şekilde yapılandırabilirsiniz :

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

11
Bunu nasıl yapacağımı biliyorum. Davranış istediğim gibi değil (soruda belirtildiği gibi) - "Bu, Windows hizmetine istekte bulunur, ancak kimlik bilgilerini doğru bir şekilde iletmez (hizmet kullanıcıyı IIS APPPOOL \ ASP.NET 4.0 olarak bildirir). olmasını istediğim şey değil. "
adrianbanks

4
Bu, iis'in yalnızca Windows kimlik doğrulamasının etkin olduğu sorunumu düzeltiyor gibi görünüyor. Sadece bazı yasal kimlik bilgilerine ihtiyacınız varsa, bunu yapmalısınız.
Timmerz

Kimliğe bürünme / temsilci seçme senaryolarında bunun WebClient ile aynı şekilde çalıştığından emin değilim. Yukarıdaki çözüm ile HttpClient kullanırken "Hedef asıl adı yanlış", ancak benzer bir kurulum ile WebClient kullanarak kullanıcının kimlik bilgilerini geçer.
Peder Rice

Bu benim için çalıştı ve günlükleri doğru kullanıcı gösterir. Her ne kadar, resimde çift sıçrama ile, temel kimlik doğrulama şeması olarak NTLM ile çalışmasını beklemiyordum, ancak işe yarıyor.
Nitin Rastogi

Aspnet çekirdeğinin en son sürümünü kullanarak aynı şeyi nasıl yapabilirim? (2.2). Kimse bilirse ...
Nico

26

Yapmaya çalıştığınız şey, NTLM'nin kimliğini yapamayacağı bir sonraki sunucuya iletmesini sağlamaktır - yalnızca size yerel kaynaklara erişim sağlayan kimliğe bürünme yapabilir. Bir makine sınırını aşmanıza izin vermez. Kerberos kimlik doğrulaması, biletleri kullanarak temsilci seçmeyi (ihtiyacınız olanı) destekler ve zincirdeki tüm sunucular ve uygulamalar doğru bir şekilde yapılandırıldığında ve Kerberos etki alanında doğru bir şekilde ayarlandığında bilet iletilebilir. Yani kısaca NTLM kullanmaktan Kerberos'a geçmeniz gerekiyor.

Kullanabileceğiniz Windows Kimlik Doğrulama seçenekleri ve bunların nasıl çalıştığı hakkında daha fazla bilgi için şu adresi ziyaret edin : http://msdn.microsoft.com/en-us/library/ff647076.aspx


3
" NTLM kimliğini yapamayacağı bir sonraki sunucuya iletmek için " - bunu kullanırken WebClientnasıl oluyor? Mümkün değilse, nasıl gelip - Bu Anlamadığım şey olduğunu yapıyor?
adrianbanks

2
Web istemcisi kullanılırken, istemci ile sunucu arasında hala tek bir bağlantı vardır. Bu sunucudaki kullanıcıyı taklit edebilir (1 atlama), ancak bu kimlik bilgilerini başka bir makineye (2 atlama - istemciden sunucuya 2. sunucuya) iletemez. Bunun için delegasyona ihtiyacınız var.
BlackSpy

1
Yapmaya çalıştığınız şekilde yapmaya çalıştığınız tek yol, kullanıcının kullanıcı adını ve parolasını ASP.NET uygulamanızdaki özel bir iletişim kutusuna yazmasını sağlamak, dizeler olarak saklamak ve sonra kullanmaktır. Web API projenize bağlandığınızda kimliğinizi ayarlayın. Aksi takdirde, Kerboros biletini Web API projesine geçirebilmeniz için NTLM'yi bırakmanız ve Kerberos'a geçmeniz gerekir. Orijinal cevabımda eklediğim bağlantıyı okumanızı şiddetle tavsiye ederim. Yapmaya çalıştığınız şey başlamadan önce güçlü bir Windows kimlik doğrulaması anlayışını gerektirir.
BlackSpy

2
@BlackSpy: Windows Kimlik Doğrulaması konusunda bol deneyimim var. Anlamaya çalıştığım neden WebClientNTLM kimlik bilgilerini iletebiliyor, ama HttpClientyapamıyorum. Bunu sadece ASP.Net kimliğe bürünme özelliğini kullanarak ve Kerberos kullanmak veya kullanıcı adlarını / parolaları depolamak zorunda kalmadan başarabilirim. Ancak bu sadece ile çalışır WebClient.
adrianbanks

1
Kullanıcı adını ve şifreyi metin olarak iletmeden 1'den fazla atlamada kimliğe bürünmek imkansız olmalıdır. Kimliğe Bürünme kurallarını ihlal eder ve NTLM buna izin vermez. WebClient, kimlik bilgilerini ilettiğiniz ve kutuda bu kullanıcı olarak çalıştığınız için 1 atlama atlamanıza olanak tanır. Güvenlik günlüklerine bakarsanız, oturum açma bilgilerini görürsünüz - kullanıcı sistemde oturum açar. Daha sonra, kimlik bilgilerini metin olarak geçirmediyseniz ve sonraki kutuya oturum açmak için başka bir web istemcisi örneği kullanmadıysanız, o makineden o kullanıcı olarak çalışamazsınız.
BlackSpy

17

Tamam, yukarıdaki tüm katılımcılara teşekkürler. .NET 4.6 kullanıyorum ve aynı sorunu yaşadık. Ben hata ayıklama System.Net.Http, özellikle, zaman geçirdim HttpClientHandlerve aşağıdakileri buldum:

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

Bu nedenle ExecutionContext.IsFlowSuppressed(), suçlu olabileceğini değerlendirdikten sonra , Kimliğe Bürünme kodumuzu aşağıdaki gibi tamamladım:

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

İçindeki kod SafeCaptureIdenity(imla hatam değil), WindowsIdentity.Current()bizim kimliğimizin kimliğidir. Bu toplanıyor çünkü şimdi akışı bastırıyoruz. Kullanım / atma nedeniyle, çağırma işleminden sonra sıfırlanır.

Şimdi bizim için çalışıyor gibi görünüyor, vay!


2
Bu analizi yaptığınız için çok teşekkür ederim. Bu benim durumumu da düzeltti. Kimliğim diğer web uygulamasına doğru bir şekilde aktarıldı! Bana saatler süren işten tasarruf ettin! Kene sayısında daha yüksek olmadığına şaşırdım.
justdan23

Sadece ihtiyacım vardı using (System.Threading.ExecutionContext.SuppressFlow())ve sorun benim için çözüldü!
ZX9

10

.NET Core'da, kimliği doğrulanmış kullanıcının Windows kimlik bilgilerini kullanarak bir arka uç hizmetine geçmek için bir System.Net.Http.HttpClientile başlamayı başardım .UseDefaultCredentials = trueWindowsIdentity.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);
    });
}

4

Windows hizmetinde internet erişimi olan bir kullanıcı kurduktan sonra benim için çalıştı.

Kodumda:

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

3

Tamam, Joshoun kodunu alıp jenerik yaptım. SynchronousPost sınıfında singleton desen uygulamak gerekir emin değilim. Belki daha bilgili biri yardımcı olabilir.

uygulama

// Sanırım kendi beton tipin var. Benim durumumda ilk olarak FileCategory adında bir sınıfla kod kullanıyorum

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

Genel Sınıf burada. İstediğiniz türü geçebilirsiniz

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

Merak ediyorsanız Api sınıflarım şöyle görünüyor

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

Birimi ile ninject ve repo desenini kullanıyorum. Her neyse, yukarıdaki jenerik sınıf gerçekten yardımcı oluyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.