C# 如何让 HttpClient 与请求一起传递凭据?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12212116/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to get HttpClient to pass credentials along with the request?
提问by adrianbanks
I have a web application (hosted in IIS) that talks to a Windows service. The Windows service is using the ASP.Net MVC Web API (self-hosted), and so can be communicated with over http using JSON. The web application is configured to do impersonation, the idea being that the user who makes the request to the web application should be the user that the web application uses to make the request to the service. The structure looks like this:
我有一个与 Windows 服务通信的 Web 应用程序(托管在 IIS 中)。Windows 服务使用 ASP.Net MVC Web API(自托管),因此可以使用 JSON 通过 http 进行通信。Web 应用程序配置为进行模拟,其想法是向 Web 应用程序发出请求的用户应该是 Web 应用程序用来向服务发出请求的用户。结构如下所示:


(The user highlighted in red is the user being referred to in the examples below.)
(以红色突出显示的用户是以下示例中所指的用户。)
The web application makes requests to the Windows service using an HttpClient:
Web 应用程序使用以下命令向 Windows 服务发出请求HttpClient:
var httpClient = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
httpClient.GetStringAsync("http://localhost/some/endpoint/");
This makes the request to the Windows service, but does not pass the credentials over correctly (the service reports the user as IIS APPPOOL\ASP.NET 4.0). This is not what I want to happen.
这会向 Windows 服务发出请求,但没有正确传递凭据(服务将用户报告为IIS APPPOOL\ASP.NET 4.0)。这不是我想要发生的。
If I change the above code to use a WebClientinstead, the credentials of the user are passed correctly:
如果我将上面的代码更改为使用 a WebClient,则会正确传递用户的凭据:
WebClient c = new WebClient
{
UseDefaultCredentials = true
};
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));
With the above code, the service reports the user as the user who made the request to the web application.
使用上述代码,服务将用户报告为向 Web 应用程序发出请求的用户。
What am I doing wrong with the HttpClientimplementation that is causing it to not pass the credentials correctly(or is it a bug with the HttpClient)?
我在HttpClient导致它无法正确传递凭据的实现上做错了什么(或者它是一个错误HttpClient)?
The reason I want to use the HttpClientis that it has an async API that works well with Tasks, whereas the WebClient's asyc API needs to be handled with events.
我想使用 的原因HttpClient是它有一个可以很好地与Tasks配合使用的异步 API ,而WebClient的异步 API 需要使用事件来处理。
采纳答案by Joshua
I was also having this same problem. I developed a synchronous solution thanks to the research done by @tpeczek in the following SO article: Unable to authenticate to ASP.NET Web Api service with HttpClient
我也遇到了同样的问题。由于@tpeczek 在以下 SO 文章中进行的研究,我开发了一个同步解决方案:无法使用 HttpClient 对 ASP.NET Web Api 服务进行身份验证
My solution uses a WebClient, which as you correctly noted passes the credentials without issue. The reason HttpClientdoesn't work is because of Windows security disabling the ability to create new threads under an impersonated account (see SO article above.) HttpClientcreates new threads via the Task Factory thus causing the error. WebClienton the other hand, runs synchronously on the same thread thereby bypassing the rule and forwarding its credentials.
我的解决方案使用WebClient,正如您正确指出的那样,它可以毫无问题地传递凭据。原因HttpClient不起作用是因为 Windows 安全性禁用了在模拟帐户下创建新线程的能力(请参阅上面的 SO 文章。) HttpClient通过任务工厂创建新线程从而导致错误。 WebClient另一方面,在同一线程上同步运行,从而绕过规则并转发其凭据。
Although the code works, the downside is that it will not work async.
虽然代码有效,但缺点是它不能异步工作。
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();
}
Note:Requires NuGet package: Newtonsoft.Json, which is the same JSON serializer WebAPI uses.
注意:需要 NuGet 包:Newtonsoft.Json,它与 WebAPI 使用的 JSON 序列化程序相同。
回答by BlackSpy
What you are trying to do is get NTLM to forward the identity on to the next server, which it cannot do - it can only do impersonation which only gives you access to local resources. It won't let you cross a machine boundary. Kerberos authentication supports delegation (what you need) by using tickets, and the ticket can be forwarded on when all servers and applications in the chain are correctly configured and Kerberos is set up correctly on the domain. So, in short you need to switch from using NTLM to Kerberos.
您想要做的是让 NTLM 将身份转发到下一个服务器,但它不能这样做 - 它只能进行模拟,这只能让您访问本地资源。它不会让你跨越机器边界。Kerberos 身份验证通过使用票证支持委派(您需要什么),并且当链中的所有服务器和应用程序都正确配置并且 Kerberos 在域上正确设置时,票证可以被转发。因此,简而言之,您需要从使用 NTLM 切换到 Kerberos。
For more on Windows Authentication options available to you and how they work start at: http://msdn.microsoft.com/en-us/library/ff647076.aspx
有关可用的 Windows 身份验证选项及其工作原理的更多信息,请访问:http: //msdn.microsoft.com/en-us/library/ff647076.aspx
回答by hidden
Ok so I took Joshoun code and made it generic. I am not sure if I should implement singleton pattern on SynchronousPost class. Maybe someone more knowledgeble can help.
好的,所以我采用了 Joshoun 代码并使其通用。我不确定是否应该在 SynchronousPost 类上实现单例模式。也许知识渊博的人可以提供帮助。
Implementation
执行
//我假设你有自己的具体类型。就我而言,我首先将代码与名为 FileCategory 的类一起使用FileCategory x = new FileCategory { CategoryName = "Some Bs"};
SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>();
test.PostEntity(x, "/api/ApiFileCategories");
Generic Class here. You can pass any type
通用类在这里。您可以传递任何类型
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; }
}
My Api classs looks like this, if you are curious
我的 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();
}
}
I am using ninject, and repo pattern with unit of work. Anyways, the generic class above really helps.
我正在使用 ninject 和带有工作单元的 repo 模式。无论如何,上面的泛型类确实有帮助。
回答by Sean
You can configure HttpClientto automatically pass credentials like this:
您可以配置HttpClient为自动传递这样的凭据:
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
回答by Chullybun
OK, so thanks to all of the contributors above. I am using .NET 4.6 and we also had the same issue. I spent time debugging System.Net.Http, specifically the HttpClientHandler, and found the following:
好的,感谢以上所有贡献者。我正在使用 .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);
}
So after assessing that the ExecutionContext.IsFlowSuppressed()might have been the culprit, I wrapped our Impersonation code as follows:
因此,在评估ExecutionContext.IsFlowSuppressed()可能是罪魁祸首之后,我将我们的 Impersonation 代码包装如下:
using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate())
using (System.Threading.ExecutionContext.SuppressFlow())
{
// HttpClient code goes here!
}
The code inside of SafeCaptureIdenity(not my spelling mistake), grabs WindowsIdentity.Current()which is our impersonated identity. This is being picked up because we are now suppressing flow. Because of the using/dispose this is reset after invocation.
里面的代码SafeCaptureIdenity(不是我的拼写错误),抓住WindowsIdentity.Current()了我们假冒的身份。这是因为我们现在正在抑制流量。由于 using/dispose 这在调用后被重置。
It now seems to work for us, phew!
它现在似乎对我们有用,呸!
回答by Paulo Augusto Batista
It worked for me after I set up a user with internet access in the Windows service.
我在 Windows 服务中设置了一个可以访问互联网的用户后,它对我有用。
In my code:
在我的代码中:
HttpClientHandler handler = new HttpClientHandler();
handler.Proxy = System.Net.WebRequest.DefaultWebProxy;
handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
.....
HttpClient httpClient = new HttpClient(handler)
....
回答by scourge192
In .NET Core, I managed to get a System.Net.Http.HttpClientwith UseDefaultCredentials = trueto pass through the authenticated user's Windows credentials to a back end service by using WindowsIdentity.RunImpersonated.
在.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);
});
}

