Làm cách nào để có được httpClient để vượt qua thông tin đăng nhập cùng với yêu cầu?


164

Tôi có một ứng dụng web (được lưu trữ trong IIS) để nói chuyện với một dịch vụ Windows. Dịch vụ Windows đang sử dụng API Web MVC của ASP.Net (tự lưu trữ) và do đó có thể được liên lạc qua http bằng JSON. Ứng dụng web được cấu hình để thực hiện mạo danh, ý tưởng là người dùng đưa ra yêu cầu cho ứng dụng web phải là người dùng mà ứng dụng web sử dụng để thực hiện yêu cầu dịch vụ. Cấu trúc trông như thế này:

(Người dùng được tô sáng màu đỏ là người dùng được nhắc đến trong các ví dụ bên dưới.)


Ứng dụng web tạo yêu cầu cho dịch vụ Windows bằng cách sử dụng HttpClient:

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

Điều này thực hiện yêu cầu đối với dịch vụ Windows, nhưng không chuyển thông tin xác thực một cách chính xác (dịch vụ báo cáo người dùng là IIS APPPOOL\ASP.NET 4.0). Đây không phải là điều tôi muốn xảy ra .

Nếu tôi thay đổi mã trên để sử dụng WebClientthay thế, thông tin đăng nhập của người dùng sẽ được chuyển chính xác:

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

Với đoạn mã trên, dịch vụ báo cáo người dùng là người dùng đã yêu cầu ứng dụng web.

Tôi đang làm gì sai với việc HttpClienttriển khai khiến nó không vượt qua được thông tin đăng nhập một cách chính xác (hoặc đó là một lỗi với HttpClient)?

Lý do tôi muốn sử dụng HttpClientlà vì nó có API async hoạt động tốt với Tasks, trong khi WebClientAPI asyc cần phải được xử lý với các sự kiện.


Bản sao có thể có của stackoverflow.com/q/10308938/1045628
Tommy Grovnes

Có vẻ như httpClient và WebClient coi những thứ khác nhau là DefaultCredentials. Bạn đã thử dùng httpClient.setCredentials (...) chưa?
Germann Arlington

BTW, WebClient có DownloadStringTaskAsync.Net 4.5, cũng có thể được sử dụng với async / await
LB

1
@GermannArlington: HttpClientkhông có SetCredentials()phương pháp. Bạn có thể chỉ cho tôi những gì bạn có ý nghĩa?
adrianbanks

4
Có vẻ như điều này đã được sửa (.net 4.5.1)? Tôi đã thử tạo new HttpClient(new HttpClientHandler() { AllowAutoRedirect = true, UseDefaultCredentials = true }trên máy chủ web được truy cập bởi người dùng được xác thực Windows và trang web đã xác thực cho một tài nguyên từ xa khác sau đó (sẽ không xác thực nếu không đặt cờ).
GSerg

Câu trả lời:


67

Tôi cũng gặp vấn đề tương tự. Tôi đã phát triển một giải pháp đồng bộ nhờ vào nghiên cứu được thực hiện bởi @tpeczek trong bài viết SO sau: Không thể xác thực dịch vụ Api ASP.NET Web với HttpClient

Giải pháp của tôi sử dụng một WebClient, như bạn lưu ý chính xác vượt qua các thông tin mà không có vấn đề. Lý do HttpClientkhông hoạt động là do bảo mật của Windows vô hiệu hóa khả năng tạo các luồng mới trong tài khoản bị mạo danh (xem bài viết SO ở trên.) HttpClientTạo các luồng mới thông qua Nhà máy tác vụ do đó gây ra lỗi. WebClientmặt khác, chạy đồng bộ trên cùng một luồng do đó bỏ qua quy tắc và chuyển tiếp thông tin đăng nhập của nó.

Mặc dù mã hoạt động, nhược điểm là nó sẽ không hoạt động không đồng bộ.

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

Lưu ý: Yêu cầu gói NuGet: Newtonsoft.Json, đây là trình tuần tự JSON mà WebAPI sử dụng.


1
Tôi đã làm một cái gì đó tương tự cuối cùng, và nó hoạt động thực sự tốt. Vấn đề không đồng bộ không phải là vấn đề, vì tôi muốn các cuộc gọi bị chặn.
adrianbanks

136

Bạn có thể định cấu hình HttpClientđể tự động chuyển thông tin đăng nhập như thế này:

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

11
Tôi biết làm thế nào để làm điều đó. Hành vi không phải là điều tôi muốn (như đã nêu trong câu hỏi) - "Điều này thực hiện yêu cầu đối với dịch vụ Windows, nhưng không chuyển thông tin xác thực một cách chính xác (dịch vụ báo cáo người dùng là IIS APPPOOL \ ASP.NET 4.0). không phải là điều tôi muốn xảy ra. "
adrianbanks

4
điều này dường như khắc phục vấn đề của tôi khi iis chỉ kích hoạt xác thực windows. nếu bạn chỉ cần một số thông tin hợp pháp được thông qua, điều này sẽ làm điều đó.
Timmerz

Không chắc chắn điều này hoạt động giống như WebClient trong các tình huống mạo danh / ủy quyền. Tôi nhận được "Tên chính của mục tiêu không chính xác" khi sử dụng HttpClient với giải pháp trên, nhưng sử dụng WebClient với thiết lập tương tự sẽ vượt qua thông tin đăng nhập của người dùng.
Gạo Peder

Điều này đã làm việc cho tôi và các bản ghi hiển thị người dùng chính xác. Mặc dù, với hop kép trong ảnh, tôi không mong đợi nó hoạt động với NTLM như sơ đồ xác thực cơ bản, nhưng nó hoạt động.
Nitin Rastogi

Làm thế nào để làm điều tương tự bằng cách sử dụng phiên bản mới nhất của lõi aspnet? (2.2). Nếu có ai biết ...
Nico

26

Những gì bạn đang cố gắng làm là đưa NTLM chuyển tiếp danh tính đến máy chủ tiếp theo, điều mà nó không thể làm - nó chỉ có thể thực hiện mạo danh mà chỉ cung cấp cho bạn quyền truy cập vào tài nguyên cục bộ. Nó sẽ không cho phép bạn vượt qua một ranh giới máy. Xác thực Kerberos hỗ trợ ủy quyền (những gì bạn cần) bằng cách sử dụng vé và vé có thể được chuyển tiếp khi tất cả các máy chủ và ứng dụng trong chuỗi được định cấu hình chính xác và Kerberos được thiết lập chính xác trên miền. Vì vậy, trong ngắn hạn, bạn cần chuyển từ sử dụng NTLM sang Kerberos.

Để biết thêm về các tùy chọn Xác thực Windows có sẵn cho bạn và cách chúng hoạt động bắt đầu tại: http://msdn.microsoft.com/en-us/l Library / ff647076.aspx


3
" NTLM để chuyển tiếp danh tính đến máy chủ tiếp theo, điều mà nó không thể làm được " - tại sao nó lại làm điều này khi sử dụng WebClient? Đây là điều tôi không hiểu - nếu nó không phải là tốt, sao nó được làm việc đó?
adrianbanks

2
Khi sử dụng máy khách web, nó vẫn chỉ là một kết nối, giữa máy khách và máy chủ. Nó có thể mạo danh người dùng trên máy chủ đó (1 hop), nhưng không thể chuyển tiếp các thông tin đó sang máy khác (2 bước nhảy - máy khách đến máy chủ đến máy chủ thứ 2). Cho rằng bạn cần đoàn.
BlackSpy

1
Cách duy nhất để thực hiện những gì bạn đang cố gắng thực hiện theo cách bạn đang cố gắng là bắt người dùng nhập tên người dùng và mật khẩu của mình vào hộp thoại tùy chỉnh trên ứng dụng ASP.NET của bạn, lưu trữ chúng dưới dạng chuỗi và sau đó sử dụng chúng để đặt danh tính của bạn khi bạn kết nối với dự án API Web của bạn. Nếu không, bạn cần bỏ NTLM và di chuyển đến Kerberos, để bạn có thể chuyển vé Kerboros cho dự án API Web. Tôi rất khuyên bạn nên đọc liên kết tôi đính kèm trong câu trả lời ban đầu của tôi. Những gì bạn đang cố gắng làm đòi hỏi một sự hiểu biết mạnh mẽ về xác thực windows trước khi bạn bắt đầu.
BlackSpy

2
@BlackSpy: Tôi có nhiều kinh nghiệm với Xác thực Windows. Những gì tôi đang cố gắng hiểu là tại sao WebClientcó thể vượt qua thông tin đăng nhập NTLM, nhưng HttpClientkhông thể. Tôi có thể đạt được điều này bằng cách sử dụng mạo danh ASP.Net một mình và không phải sử dụng Kerberos hoặc lưu trữ tên người dùng / mật khẩu. Điều này tuy nhiên chỉ hoạt động với WebClient.
adrianbanks

1
Không thể mạo danh hơn 1 hop mà không chuyển tên người dùng và mật khẩu xung quanh dưới dạng văn bản. nó phá vỡ các quy tắc mạo danh và NTLM sẽ không cho phép điều đó. WebClient cho phép bạn nhảy 1 hop vì bạn chuyển thông tin đăng nhập và chạy như người dùng đó trên hộp. Nếu bạn nhìn vào nhật ký bảo mật, bạn sẽ thấy thông tin đăng nhập - người dùng đăng nhập vào hệ thống. Sau đó, bạn không thể chạy với tư cách là người dùng đó từ máy đó trừ khi bạn đã chuyển thông tin đăng nhập dưới dạng văn bản và sử dụng một phiên bản webclient khác để đăng nhập vào hộp tiếp theo.
BlackSpy

17

OK, cảm ơn tất cả những người đóng góp ở trên. Tôi đang sử dụng .NET 4.6 và chúng tôi cũng gặp vấn đề tương tự. Tôi đã dành thời gian gỡ lỗi System.Net.Http, cụ thể là HttpClientHandler, và tìm thấy như sau:

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

Vì vậy, sau khi đánh giá rằng đó ExecutionContext.IsFlowSuppressed()có thể là thủ phạm, tôi đã bọc mã Mạo danh của chúng tôi như sau:

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

Mã bên trong SafeCaptureIdenity(không phải lỗi chính tả của tôi), lấy WindowsIdentity.Current()đó là danh tính mạo danh của chúng tôi. Điều này đang được chọn bởi vì chúng tôi hiện đang ngăn chặn dòng chảy. Bởi vì việc sử dụng / vứt bỏ nó được đặt lại sau khi gọi.

Bây giờ có vẻ như làm việc cho chúng tôi, phew!


2
Cảm ơn bạn rất nhiều vì đã làm phân tích này. Điều này đã khắc phục tình trạng của tôi quá. Bây giờ Danh tính của tôi được chuyển chính xác sang ứng dụng web khác! Bạn đã tiết kiệm cho tôi hàng giờ làm việc! Tôi ngạc nhiên khi nó không cao hơn về số lượng đánh dấu.
justdan23

Tôi chỉ cần using (System.Threading.ExecutionContext.SuppressFlow())và vấn đề đã được giải quyết cho tôi!
ZX9

10

Trong NET Core, tôi quản lý để có được một System.Net.Http.HttpClientvới UseDefaultCredentials = trueđi qua thông tin quan trọng của người dùng Windows xác thực để một dịch vụ trở lại cuối cùng bằng cách sử dụng 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);
    });
}

4

Nó hoạt động với tôi sau khi tôi thiết lập người dùng có quyền truy cập internet trong dịch vụ Windows.

Trong mã của tôi:

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

3

Ok vì vậy tôi lấy mã Joshoun và làm cho nó chung chung. Tôi không chắc chắn liệu tôi có nên triển khai mẫu singleton trên lớp SynousPost hay không. Có lẽ ai đó hiểu biết nhiều hơn có thể giúp đỡ.

Thực hiện

// Tôi giả sử bạn có loại bê tông của riêng bạn. Trong trường hợp của tôi, trước tiên tôi đang sử dụng mã với một lớp có tên là FileC Category

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

Lớp học chung ở đây. Bạn có thể vượt qua bất kỳ loại

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

Các lớp Api của tôi trông như thế này, nếu bạn tò mò

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

Tôi đang sử dụng ninject, và repo mẫu với đơn vị công việc. Dù sao, lớp học chung ở trên thực sự có ích.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.