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


164

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는 이벤트로 처리해야하기 때문입니다.



HttpClient와 WebClient는 다른 것을 DefaultCredentials로 간주합니다. HttpClient.setCredentials (...) 시도 했습니까?
Germann Arlington

BTW, WebClient는 DownloadStringTaskAsync.Net 4.5에 있으며 async / await와 함께 사용할 수도 있습니다
LB

1
@ GermannArlington : 방법 HttpClient이 없습니다 SetCredentials(). 무슨 말인지 말해 주실 래요?
adrianbanks

4
이것이 수정 된 것 같습니다 (.net 4.5.1)? new HttpClient(new HttpClientHandler() { AllowAutoRedirect = true, UseDefaultCredentials = true }Windows 인증 사용자가 액세스 한 웹 서버에서 작성 을 시도한 후 웹 사이트가 그 후 다른 원격 자원을 인증했습니다 (플래그 세트없이 인증 할 수 없음).
GSerg

답변:


67

나는 또한이 같은 문제가 발생했습니다. 다음 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에서 사용하는 것과 동일합니다.


1
나는 결국 비슷한 것을했고 실제로 잘 작동합니다. 호출을 차단하기를 원하므로 비동기 문제는 문제가되지 않습니다.
adrianbanks

136

다음 HttpClient과 같이 자격 증명을 자동으로 전달 하도록 구성 할 수 있습니다 .

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

11
나는 그것을하는 방법을 알고있다. 동작은 내가 원하는 것이 아닙니다 (질문에 설명 된대로)- "이는 Windows 서비스에 요청을하지만 자격 증명을 올바르게 전달하지 않습니다 (서비스는 사용자를 IIS APPPOOL \ ASP.NET 4.0으로보고합니다). 내가 일어나고 싶은 것이 아닙니다. "
adrianbanks

4
이것은 iis에 Windows 인증 만 활성화 된 문제를 해결하는 것으로 보입니다. 합법적 인 자격 증명을 전달하기 만하면됩니다.
Timmerz

가장 / 위임 시나리오에서 WebClient와 동일하게 작동하는지 확실하지 않습니다. 위의 솔루션으로 HttpClient를 사용할 때 "대상 주체 이름이 잘못되었습니다"라는 메시지가 표시되지만 비슷한 설정으로 WebClient를 사용하면 사용자의 자격 증명이 전달됩니다.
Peder Rice

이것은 나를 위해 일했고 로그는 올바른 사용자를 보여줍니다. 이중 홉을 사용하면 기본 인증 체계로 NTLM과 함께 작동 할 것으로 기대하지는 않았지만 작동합니다.
Nitin Rastogi

최신 버전의 aspnet core를 사용하여 동일한 작업을 수행하는 방법은 무엇입니까? (2.2). 누구나 알고 있다면 ...
Nico

26

NTLM이 다음 서버로 ID를 전달하도록하는 것입니다.이 서버는 할 수 없습니다. 로컬 리소스에만 액세스 할 수있는 가장 만 할 수 있습니다. 기계 경계를 넘을 수 없습니다. Kerberos 인증은 티켓을 사용하여 위임 (필요한 것)을 지원하며 체인의 모든 서버와 응용 프로그램이 올바르게 구성되고 도메인에서 Kerberos가 올바르게 설정되면 티켓을 전달할 수 있습니다. 즉, NTLM 사용에서 Kerberos로 전환해야합니다.

사용 가능한 Windows 인증 옵션 및 작동 방식에 대한 자세한 내용은 http://msdn.microsoft.com/en-us/library/ff647076.aspx를 참조하십시오.


3
" 다음 서버에 ID를 전달하기 위해 NTLM을 사용할 수 없습니다. "-사용할 때 WebClient어떻게됩니까? 그것이 가능하지 않은 경우, 어떻게 올 - 이것은 내가 이해하지 않는 일이 되어 그 일을?
adrianbanks

2
웹 클라이언트를 사용하는 경우 클라이언트와 서버 간의 연결은 여전히 ​​하나입니다. 해당 서버의 사용자를 가장 할 수 있지만 (1 홉) 해당 자격 증명을 다른 컴퓨터 (2 홉-클라이언트에서 서버로 두 번째 서버)로 전달할 수 없습니다. 이를 위해서는 위임이 필요합니다.
BlackSpy

1
수행하려는 방식으로 수행하려는 작업을 수행하는 유일한 방법은 사용자가 자신의 사용자 이름과 암호를 ASP.NET 응용 프로그램의 사용자 지정 대화 상자에 입력하여 문자열로 저장 한 다음 사용하는 것입니다. 웹 API 프로젝트에 연결할 때 ID를 설정합니다. 그렇지 않으면 NTLM을 삭제하고 Kerberos로 이동해야 Kerboros 티켓을 웹 API 프로젝트로 전달할 수 있습니다. 원래 답변에 첨부 된 링크를 읽는 것이 좋습니다. 수행하려는 작업을 시작하기 전에 Windows 인증에 대한 강력한 이해가 필요합니다.
BlackSpy

2
@BlackSpy : Windows 인증 경험이 풍부합니다. 내가 이해하려고하는 것은 왜 WebClientNTLM 자격 증명을 전달할 HttpClient수 있지만 그렇지는 않느냐 입니다. ASP.Net 가장 만 사용하여이 작업을 수행 할 수 있으며 Kerberos를 사용하거나 사용자 이름 / 암호를 저장할 필요가 없습니다. 그러나 이것은 에서만 작동합니다 WebClient.
adrianbanks

1
사용자 이름과 비밀번호를 텍스트로 전달하지 않고 1 회 이상 홉으로 가장하는 것은 불가능합니다. 가장 규칙을 위반하므로 NTLM에서 허용하지 않습니다. 자격 증명을 전달하고 상자에서 해당 사용자로 실행하기 때문에 WebClient를 사용하면 1 홉으로 이동할 수 있습니다. 보안 로그를 보면 사용자가 시스템에 로그인하는 로그인이 표시됩니다. 자격 증명을 텍스트로 전달하고 다른 웹 클라이언트 인스턴스를 사용하여 다음 상자에 로그온하지 않으면 해당 컴퓨터에서 해당 사용자로 실행할 수 없습니다.
BlackSpy

17

위의 모든 기고자에게 감사합니다. .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()는 우리의 가장 된 신원을 포착 합니다. 우리는 지금 흐름을 억제하고 있기 때문에 선택되었습니다. 사용 / 처리 때문에 호출 후 재설정됩니다.

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


2
이 분석을 해주셔서 감사합니다. 이로 인해 내 상황도 해결되었습니다. 이제 내 ID가 다른 웹 응용 프로그램으로 올바르게 전달되었습니다! 작업 시간을 절약했습니다! 틱 카운트가 높지 않은 것에 놀랐습니다.
justdan23

나는 단지 필요 using (System.Threading.ExecutionContext.SuppressFlow())했고 문제는 나를 위해 해결되었습니다!
ZX9

10

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

4

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

내 코드에서 :

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

3

그래서 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 패턴을 사용하고 있습니다. 어쨌든 위의 일반 클래스가 실제로 도움이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.