[C#] HttpClient가 요청과 함께 자격 증명을 전달하도록하는 방법은 무엇입니까?

Windows 서비스와 통신하는 웹 응용 프로그램 (IIS에서 호스팅)이 있습니다. Windows 서비스는 ASP.Net MVC 웹 API (자체 호스팅)를 사용하므로 JSON을 사용하여 http를 통해 통신 할 수 있습니다. 웹 응용 프로그램은 가장을 수행하도록 구성되며, 웹 응용 프로그램을 요청하는 사용자는 웹 응용 프로그램이 서비스를 요청하는 데 사용하는 사용자 여야한다는 생각입니다. 구조는 다음과 같습니다.

(빨간색으로 강조 표시된 사용자는 아래 예에서 언급 된 사용자입니다.)


웹 응용 프로그램은 다음을 사용하여 Windows 서비스에 요청합니다 HttpClient.

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

이렇게하면 Windows 서비스에 요청이 이루어 지지만 자격 증명이 올바르게 전달되지 않습니다 (서비스는 사용자를로보고 함 IIS APPPOOL\ASP.NET 4.0). 이것은 내가 일어나고 싶은 것이 아닙니다 .

위의 코드를 WebClient대신 사용하도록 변경하면 사용자의 자격 증명이 올바르게 전달됩니다.

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

위 코드를 사용하면 서비스에서 웹 응용 프로그램에 요청한 사용자로 사용자를보고합니다.

어떻게 내가 잘못하고있는 중이 야 HttpClient제대로 자격 증명을 전달하지 않는 원인이되는 구현 (또는이와 버그를 HttpClient)?

내가 사용하려는 이유 HttpClientTasks 와 잘 작동하는 비동기 API를 가지고 있지만 WebClient의 asyc API는 이벤트로 처리해야하기 때문입니다.



답변

나는 또한이 같은 문제가 발생했습니다. 다음 SO 기사에서 @tpeczek의 연구 덕분에 동기 솔루션을 개발했습니다 .HttpClient를 사용하여 ASP.NET Web Api 서비스를 인증 할 수 없습니다

내 솔루션은을 사용하는데 WebClient, 올바르게 언급했듯이 문제없이 자격 증명을 전달합니다. HttpClient작동하지 않는 이유 는 Windows 보안으로 인해 위장 된 계정으로 새 스레드를 만들 수 없기 때문에 (위의 SO 기사 참조) 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과 같이 자격 증명을 자동으로 전달 하도록 구성 할 수 있습니다 .

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


답변

NTLM이 다음 서버로 ID를 전달하도록하는 것입니다.이 서버는 할 수 없습니다. 로컬 리소스에만 액세스 할 수있는 가장 만 할 수 있습니다. 기계 경계를 넘을 수 없습니다. 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(내 철자 실수가 아닌) 내부의 코드 WindowsIdentity.Current()는 우리의 가장 된 신원을 포착 합니다. 우리는 지금 흐름을 억제하고 있기 때문에 선택되었습니다. 사용 / 처리 때문에 호출 후 재설정됩니다.

그것은 지금 우리를 위해 작동하는 것 같습니다, 휴!


답변

.NET 코어에서, 나는 그럭저럭 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);
    });
}


답변

Windows 서비스에서 인터넷에 액세스 할 수있는 사용자를 설정 한 후에 그것은 나를 위해 일했습니다.

내 코드에서 :

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


답변

그래서 Joshoun 코드를 가져 와서 일반으로 만들었습니다. 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 및 repo 패턴을 사용하고 있습니다. 어쨌든 위의 일반 클래스가 실제로 도움이됩니다.