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)?

내가 사용하려는 이유 HttpClient는 Tasks 와 잘 작동하는 비동기 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.