如何让HttpClient与请求一起传递凭据?

时间:2012-08-31 09:02:26

标签: c# asp.net-web-api impersonation windows-security

我有一个与Windows服务对话的Web应用程序(在IIS中托管)。 Windows服务使用ASP.Net MVC Web API(自托管),因此可以使用JSON通过http进行通信。 Web应用程序配置为进行模拟,其想法是向Web应用程序发出请求的用户应该是Web应用程序用来向服务发出请求的用户。结构如下所示:

(以红色突出显示的用户是以下示例中引用的用户。)


Web应用程序使用HttpClient

向Windows服务发出请求
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/"));

使用上述代码,服务将用户报告为向Web应用程序发出请求的用户。

HttpClient实施导致其无法正确传递凭据(或者是HttpClient的错误),我做错了什么?

我想使用HttpClient的原因是它有一个与Task s兼容的异步API,而WebClient的asyc API需要处理事件。

7 个答案:

答案 0 :(得分:104)

您可以将HttpClient配置为自动传递以下凭据:

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

答案 1 :(得分:53)

我也有同样的问题。由于@tpeczek在以下SO文章中所做的研究,我开发了一个同步解决方案:Unable to authenticate to ASP.NET Web Api service with HttpClient

我的解决方案使用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,它与WebAPI使用的JSON序列化程序相同。

答案 2 :(得分:24)

您要做的是让NTLM将身份转发到下一个服务器上,这是不能做的 - 它只能进行模拟,只能让您访问本地资源。它不会让你跨越机器边界。 Kerberos身份验证通过使用故障单支持委派(您需要),并且当链中的所有服务器和应用程序都已正确配置并且在域上正确设置Kerberos时,可以转发故障单。 因此,简而言之,您需要从使用NTLM切换到Kerberos。

有关您可以使用的Windows身份验证选项以及它们如何工作的详细信息,请参阅: http://msdn.microsoft.com/en-us/library/ff647076.aspx

答案 3 :(得分:8)

好的,感谢上面的所有贡献者。我使用的是.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()这是我们的模仿身份。这是因为我们现在正在抑制流动。由于使用/ dispose,这在调用后重置。

它现在似乎对我们有用,p !.

答案 4 :(得分:4)

在.NET Core中,我设法通过System.Net.Http.HttpClient获得UseDefaultCredentials = true,通过WindowsIdentity.RunImpersonated将经过身份验证的用户的Windows凭据传递给后端服务。< / p>

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

答案 5 :(得分: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模式。无论如何,上面的通用类确实有帮助。

答案 6 :(得分:3)

在Windows服务中设置了具有Internet访问权限的用户后,它对我有用。

在我的代码中:

UPDATE wp_postmeta SET meta_value = replace(meta_value,'/wp-content/upload',' https://example.com/wp-content/uploads') where meta_key = 'wpcf-image';