【问题标题】:HTTPClient or WebClient for Windows Authentication用于 Windows 身份验证的 HTTPClient 或 WebClient
【发布时间】:2018-03-02 17:08:45
【问题描述】:

我正在构建一个内部应用程序,它将使用 Web Api 与前端应用程序进行通信,并且我还计划使用 Windows 身份验证。

现在我正在使用 WebClient,它可以很好地处理模拟。

有没有人能够在不阻塞和进行异步调用的情况下使用 HTTPClient 做到这一点?在我看来,HTTPClient 意味着在应用程序生命周期内被实例化一次,这对于让每个请求都通过模拟具有唯一身份验证可能并不是决定性的。

我尝试了下面的代码,但我认为“GetStringAsync”方法是创建新任务的原因,我不知道如何使用模拟帐户而不是应用程序池帐户。

WindowsIdentity impersonatedUser = WindowsIdentity.GetCurrent();
using (WindowsImpersonationContext ctx = impersonatedUser.Impersonate())
{
Test = RequestRouter.Client.GetStringAsync("ServiceURL").Result;
}

【问题讨论】:

  • 如果我是你,我会避免使用原始 HttpClient。查看Flurl.HttpRestSharp。它们避免使用some of the dangers of using HttpClient wrong,并提供更清晰的抽象。
  • @mason 谢谢。我会试试 RestSharp。
  • 我更喜欢 Flurl,但每个人都有自己的。
  • @mason 我两个都试试!
  • 我认为您错过了我所说的重点。服务本身不会被最终用户直接访问(对吗?)Web 应用程序是用户要进行身份验证的对象。因此,让 Web 应用程序将用户的用户名传递给服务,服务可以使用该信息进行授权。这完全消除了实际进行模拟的需要。它将其更改为您的 Web 应用程序代表特定用户而不是特定用户发出请求的模型。

标签: asp.net asp.net-web-api windows-authentication dotnet-httpclient


【解决方案1】:

我们有类似的需求,我们有一个用户与之交互的 Web 应用程序,并且该 Web 应用程序代表用户对服务进行 Web API 调用。我们没有尝试模拟(这非常棘手),而是通过 HTTP 标头将用户名从 Web 应用程序传递到服务来绕过它。该服务检查标头 - 如果它没有看到标头,则会引发错误。我们有一些逻辑可注入到我们的控制器中,以便在需要时从标题中获取用户名。

我们将 ASP.NET Core 用于我们的服务,但同样的想法也适用于 ASP.NET。

这是我们用来确保标头存在的中间件,如果存在,则抓取并存储它:

public class RequireUsernameHeaderMiddleware
{
    private const string HEADER_NAME = "hps-username";
    private const string API_ROOT = "/api";
    private readonly RequestDelegate _next;

    public RequireUsernameHeaderMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, IUsernameSetter usernameSetter)
    {
        // We only want to require the header on our API, not on our Swagger pages
        var pathString = new PathString(API_ROOT);

        if (context.Request.Path.StartsWithSegments(pathString))
        {
            if (!context.Request.Headers.Keys.Contains(HEADER_NAME))
            {
                context.Response.StatusCode = StatusCodes.Status400BadRequest;
                await context.Response.WriteAsync("Username is missing");
                return;
            }

            usernameSetter.Username = context.Request.Headers[HEADER_NAME];
        }

        await _next.Invoke(context);
    }
}

然后是胶水:

public interface IUsernameSetter
{
    string Username { set; }
}

public class UsernameProvider : IUsernameProvider, IUsernameSetter
{
    public string Username { get; set; } = String.Empty;
}

//when configuring services in Startup
services.AddScoped<UsernameProvider>();
services.AddScoped<IUsernameProvider>(service => service.GetService<UsernameProvider>());
services.AddScoped<IUsernameSetter>(service => service.GetService<UsernameProvider>());

//This gets called before app.UseMvc() in Startup when configuring the IApplicationBuilder
app.UseMiddleware<RequireUsernameHeaderMiddleware>();

现在在我们的控制器中:

readonly IUsernameProvider _usernameProvider;

public ProjectController(IUsernameProvider usernameProvider)
{
    _usernameProvider = usernameProvider;
}

public IActionResult SomeAction()
{
    _usernameProvider.Username; //now we can grab the username!
}

这不仅消除了模拟的需要,还简化了控制器的测试。

这是我们在 Web 应用程序中使用的 Flurl 代码:

return await _apiOptions.Url.AppendPathSegment("Project")
                            .WithHeader("hps-username", username)
                            .GetJsonAsync<List<Project>>();

【讨论】:

  • 谢谢@Mason。这是一个很好的答案,它肯定会为我未来的方法提供信息。我希望有一个自定义角色提供者在每个级别处理这个作为身份验证授权的跨领域解决方案,但看起来我可能需要放弃它,因为你已经走上了这条路。
  • 是什么阻止了某人将标头设置为他们喜欢的任何用户名并调用您的服务?
  • @4imble 我想你错过了在 cmets 中就这个问题进行的对话。在这种情况下,受信任的服务(例如:您控制的服务)代表用户向后端服务发出请求。显然,我们可以依赖标头中的用户名的唯一原因是我们信任调用它的服务。如果您没有该信任级别,那么您可能有一个公开的 API,然后客户端应该通过正常方式进行身份验证和授权,而无需实现此标头机制。
猜你喜欢
  • 2014-10-25
  • 2010-10-05
  • 2018-06-27
  • 1970-01-01
  • 1970-01-01
  • 2013-12-19
  • 2015-12-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多