186

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

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


Web 应用程序使用以下命令向 Windows 服务发出请求HttpClient

var httpClient = new HttpClient(new HttpClientHandler() 
                      {
                          UseDefaultCredentials = true
                      });
httpClient.GetStringAsync("http://localhost/some/endpoint/");

这会向 Windows 服务发出请求,但不会正确传递凭据(服务将用户报告为IIS APPPOOL\ASP.NET 4.0)。这不是我想要发生的。

如果我将上面的代码更改为使用 a WebClient,则用户的凭据将正确传递:

WebClient c = new WebClient
                   {
                       UseDefaultCredentials = true
                   };
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));

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

我做错了什么HttpClient导致它无法正确传递凭据(或者它是一个错误HttpClient)?

我想使用的原因HttpClient是它有一个与Tasks 配合得很好的异步 API,而 的WebClient异步 API 需要用事件来处理。

4

8 回答 8

166

您可以配置HttpClient为自动传递凭据,如下所示:

var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
于 2013-04-15T15:20:56.007 回答
73

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

我的解决方案使用 a 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 序列化程序相同。

于 2012-10-01T14:43:00.650 回答
27

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

有关您可用的 Windows 身份验证选项及其工作方式的更多信息,请访问:http: //msdn.microsoft.com/en-us/library/ff647076.aspx

于 2012-08-31T20:29:54.833 回答
23

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

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

于 2016-10-11T06:51:47.433 回答
15

在 .NET Core 中,我设法通过System.Net.Http.HttpClient使用.UseDefaultCredentials = trueWindowsIdentity.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);
    });
}
于 2018-02-06T18:49:14.310 回答
5

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

在我的代码中:

HttpClientHandler handler = new HttpClientHandler();
handler.Proxy = System.Net.WebRequest.DefaultWebProxy;
handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
.....
HttpClient httpClient = new HttpClient(handler)
.... 
于 2017-11-01T08:45:19.040 回答
3

好的,所以我采用了 Johoun 代码并使其通用。我不确定是否应该在 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 模式。无论如何,上面的泛型类确实有帮助。

于 2012-11-27T21:54:10.317 回答
0

在 web.config 中设置identity's impersonationtotruevalidateIntegratedModeConfigurationtofalse

<configuration>
  <system.web>
    <authentication mode="Windows" />
    <authorization>
      <deny users="?" />
    </authorization>
    <identity impersonate="true"/>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" ></validation>
  </system.webServer>
</configuration>
于 2022-02-06T01:40:17.047 回答