【问题标题】:HttpContext HeaderHttpContext 标头
【发布时间】:2019-04-29 21:19:27
【问题描述】:

我创建了这个类来从请求中获取 Header 值。

public class AuthenticationHeader
{
    private static  IHttpContextAccessor _httpContextAccessor;
    public AuthenticationHeader(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public string AuthHeader => _httpContextAccessor.HttpContext?.Request.Headers["Authorization"];

}

并且我已经像这样在我的 startup.cs 中注册了它

services.AddSingleton<AuthenticationHeader>();

它像这样被注入到我的其他类中。

public BaseClient(HttpClient client, ILogger<BaseClient> logger, AuthenticationHeader authHeader)
{
    _client = client;
    client.BaseAddress = new Uri("yrl");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    _logger = logger;
    AuthHeader = authHeader;
}

现在我已将其注册为 Singleton。因此,当第一次调用我的 Api 并在标头中提供授权值时,api 被成功调用,但问题是当我传递空的授权标头时,它仍然成功调用了 api,因为它由于单例而存储了旧的标头值。我怎样才能解决这个问题?有没有其他方法可以做我正在做的事情。

【问题讨论】:

  • 为什么不注册为Scoped
  • 这个解释可能有用。我建议使用范围。 stackoverflow.com/questions/38138100/…
  • 我试过了,但我的RestClient 注册为Singleton,我无法将Scoped 注入Singleton :(
  • @Shabirjan 你试过用 AddTransient 代替 AddSingleton 吗?
  • 我选择了 Scoped 而不是 Transient。谢谢@gowiththestackflow

标签: c# asp.net-core


【解决方案1】:

尝试使用在 Asp.Net Core 2.1 中添加的 HttpClientFactory 以及 HttpMessageHandler 来实现您想要做的事情。

可以在ConfigureServices方法中注册HttpClient

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<BaseClient>(client =>
    {
        client.BaseAddress = new Uri("yrl");
        client.DefaultRequestHeaders.Add("Accept", "application/json");
        c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
        c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
    });
 }

使用上述代码,您的BaseClient 将通过 DI 接收 HttpClient 实例。

为了验证/检查AuthHeader,您可以为已注册的HttpClient 配置HttpMessageHandler。消息处理程序的代码很简单,如下所示:

public class AuthHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("Authorization"))
        {
            return new HttpResponseMessage(HttpStatusCode.Forbidden)
            {
                Content = new StringContent("No Authorization header is present")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

为了注册上述处理程序,您的代码将如下所示:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<AuthHeaderHandler>();
    services.AddHttpClient<BaseClient>(client =>
     {
         //code omitted for brevity
         ...
     })
      .AddHttpMessageHandler<AuthHeaderHandler>();
 }

如果需要,您可以在消息处理程序中注入您需要的任何内容。但是,无需在BaseClient 中注入 IHttpContextAccessor。要了解有关 HttpClientFactory 和 HttpMessageHandlers 的更多信息,请参阅 linkthis。我希望这会有所帮助。

更新答案

请查看更具体的 HttpMessageHandler 示例,它使用 IHttpContextAccessor 并修改 HttpRequestMessage,即在调用之前添加 Authorization 标头。您可以根据需要修改逻辑。

public class AuthHeaderHandler : DelegatingHandler
{
    private readonly HttpContext _httpContext;

    public AuthHeaderHandler(IHttpContextAccessor contextAccessor)
    {
        _httpContext = contextAccessor.HttpContext;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (_httpContext != null)
        {
            var accessToken = await _httpContext.GetTokenAsync(TokenKeys.Access);
            if (!string.IsNullOrEmpty(accessToken))
            {
                // modify the request header with the new Authorization token
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            }
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

更新答案 2

请看一下我上传到 GitHub 的simple solution。解决方案比我最初建议的还要简单。由于您没有集成任何基于身份的身份验证/授权,您可以简单地使用 CustomActionFilter(我称之为 ValidateAuthHeader)来检查 AuthHeader 是否存在,如果不存在则返回通常的 403。

ValidateAuthHeader 中,我使用了您之前发布的中间件代码。然后,您可以简单地在需要此检查的 ActionMethods 或 Controllers 上添加此属性。

请查看DataControllerValuesControllerDataController 将接收输入的HttpClient,该HttpClient 将用于调用values 端点。 ValidateAuthHeader 出现在 GetValues 上,并将检查 AuthHeader。如果不存在,则会产生错误。

[Route("api/[controller]")]
[ApiController]
public class DataController : ControllerBase
{
    private readonly MyHttpClient _client;

    public DataController(MyHttpClient client)
    {
        _client = client;
    }

    [ValidateAuthHeader]
    public async Task<IActionResult> GetValues()
    {
        var response = await _client.GetAsync("api/values");

        var contents = await response.Content.ReadAsStringAsync();

        return new ContentResult
        {
            Content = contents,
            ContentType = "application/json",
            StatusCode = 200
        };
    }
}

流程的其余部分与我最初建议的相同。调用将通过AuthHeaderHandler 传递,这是注册MyHttpClientHttpMessageHandler。请查看Startup.cs

处理程序将通过HttpContextAccessor 检索HttpContext 并检查AuthHeader。如果存在,它会将其添加到 RequestMessage 参数中。

我希望这会有所帮助。如有任何问题,请随时提出。

不使用 HttpMessageHandler 设置 Auth Header

修改 MyHttpClient 并添加一个名为 SetAuthHeader 的公共方法

public class MyHttpClient
{
    private readonly HttpClient _httpClient;

    public MyHttpClient(HttpClient client)
    {
        _httpClient = client;
    }

    public void SetAuthHeader(string value)
    {
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", value);
    }
}

然后在您的操作方法中调用此方法,因为此时您将在 HttpContext.Request 中拥有 AuthHeader

[ValidateAuthHeader]
public async Task<IActionResult> GetValues()
{
    var authHeader = Request.Headers["Authorization"];

    _client.SetAuthHeader(authHeader.First());

    var response = await _client.GetAsync("api/values");

    var contents = await response.Content.ReadAsStringAsync();

    return new ContentResult
    {
        Content = contents,
        ContentType = "application/json",
        StatusCode = 200
    };
}

移除AuthHeaderHandler注册,删除AuthHeaderHandler。

【讨论】:

  • 感谢您的回答,但这不是我想要的。 AuthenticationHeader 用于获取标头,然后我将注入该类,因此我可以在发出授权请求时访问 AuthHeader。
  • @Shabirjan,实际上哈桑是对的。如果 AuthenticationHeader 的唯一目的是存储原始 Authorization 标头,并且 BaseClient 使用相同的标头发出请求,您可以在他提出的 AuthHeaderHandler 中注入 IHttpContextAccessor,并在那里设置 Authorization Header。并注入 HttpClient 而不是 BaseClient。如果不清楚我说什么,我可以用代码示例添加新答案。但我认为哈桑可以编辑他的答案,给你更正确的答案。
  • @Artur 根据 Hassan 的回答,我应该使用我已经在使用的 HTTPClientFactory(使用类型化客户端)。 AuthenticationHeader 的目的是存储请求中的授权标头,然后我将其注入到我的 BaseClient 中,在那里我访问 authToken 并通过将 AuthHeader 添加到我的请求中来发出请求。
  • @Artur AuthenticationHeader 的目的不是检查请求是否包含AuthHeader。相反,它的目的是从请求中获取 AuthorizationHeader 并保存它。
  • 请看我更新的答案。谢谢@Artur 的建议。
猜你喜欢
  • 2022-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 2018-05-01
相关资源
最近更新 更多