如何让HttpClient与请求一起传递证书?
我有一个Web应用程序(托pipe在IIS中),与Windows服务交谈。 Windows服务使用ASP.Net MVC Web API(自托pipe),因此可以使用JSON通过HTTP进行通信。 Web应用程序被configuration为进行模仿,其思想是向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
的原因是它有一个与Tasks很好地配合的asynchronousAPI,而WebClient
的asyc API需要用事件来处理。
我也有这个相同的问题。 我开发了一个同步解决scheme感谢@tpeczek在下面的SO文章中所做的研究: 无法通过HttpClientvalidation到ASP.NET Web Api服务
我的解决scheme使用一个WebClient
,正如您正确指出的那样传递证书没有问题。 HttpClient
不工作的原因是因为Windows安全禁止在模拟帐户下创build新线程的能力(参见上面的SO文章) HttpClient
通过Task Factory创build新线程,从而导致错误。 另一方面, WebClient
在同一个线程上同步运行,从而绕过规则并转发其凭证。
尽pipe代码起作用,但缺点是它不能工作asynchronous。
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序列化程序。
您可以将HttpClient
configuration为自动传递凭证,如下所示:
myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
你要做的是让NTLM将身份转发到下一个服务器上,而不能这样做 – 它只能做模拟,只允许你访问本地资源。 它不会让你穿过机器的边界。 Kerberos身份validation通过使用票证支持委托(您需要),并且当链中的所有服务器和应用程序都正确configuration且Kerberos在域中正确设置时,票据可以被转发。 所以,总之,您需要从使用NTLM切换到Kerberos。
有关可用于您的Windows身份validation选项以及它们如何工作的详细信息,请访问以下url : http : //msdn.microsoft.com/zh-cn/library/ff647076.aspx
好,谢谢上面所有的贡献者。 我正在使用.NET 4.6,我们也有同样的问题。 我花了时间debuggingSystem.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(),这是我们的模拟身份。 这是因为我们现在正在抑制stream量正在拾起。 由于使用/处置,这将在调用后重置。
它现在似乎为我们工作,唷!
好吧,我拿了Joshoun的代码,并把它通用。 我不确定是否应该在SynchronousPost类上实现单例模式。 也许更有知识的人可以帮忙。
履行
//我假设你有自己的具体types 在我的情况下,我有一个名为FileCategory的类首先使用代码
FileCategory x = new FileCategory { CategoryName = "Some Bs"}; SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>(); test.PostEntity(x, "/api/ApiFileCategories");
generics类在这里。 你可以通过任何types
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,并与工作单元的回购模式。 无论如何,上面的generics类真的有帮助。
在Windows服务中build立了一个可以访问互联网的用户后,它就可以工作了。
在我的代码中:
HttpClientHandler handler = new HttpClientHandler(); handler.Proxy = System.Net.WebRequest.DefaultWebProxy; handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials; ..... HttpClient httpClient = new HttpClient(handler) ....