c# - 如何使用 IHttpClientFactory 刷新 token

标签 c# asp.net-core dotnet-httpclient appsettings httpclientfactory

我使用 IHttpClientFactory 从两个使用 Net Core 2.2 的外部 API 发送请求和接收 HTTP 响应。

我正在寻找一个好的策略来使用存储在 appsettings.json 中的刷新 token 来获取新的访问 token 。当当前请求返回 403 或 401 错误时,需要请求新的 access token,当新的 access 和 refresh token 已经获取后,需要更新 appsettings.json 以使用新值,以便在后续请求中使用。

我正在使用两个客户端向两个不同的 API 发送请求,但其中只有一个使用 token 身份验证机制。

我已经实现了一些简单有效的方法,但我正在寻找一种更优雅的解决方案,可以在当前 token 过期时动态更新 header :

我已经在 Startup.ConfigureServices 方法中注册了 IHttpClientFactory,如下所示:

services.AddHttpClient();

注册后,我将以两种不同的方法使用它来调用两个不同的 API,第一种方法是:

   public async Task<AirCallRequest> GetInformationAsync(AirCallModel model)
    {
        try
        {


            CandidateResults modelCandidateResult = null;

            var request = new HttpRequestMessage(HttpMethod.Get,
            "https://*******/v2/*****");
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _appSettings.Value.Token);


            var clientJAAPI = _httpClientFactory.CreateClient();
            var responseclientJAAPI = await clientJAAPI.SendAsync(request);


            if (responseclientJAAPI.IsSuccessStatusCode)
            {
                modelCandidateResult = await responseclientJAAPI.Content
                   .ReadAsAsync<CandidateResults>();

                ....
            }


            if ((responseclientJAAPI .StatusCode.ToString() == "Unauthorized")
            {                    

                await RefreshAccessToken();

               //Calls recursively this method again
                return await GetInformationAsync(model);

            }

            return null;
        }
        catch (Exception e)
        {
            return null;

        }

    }

刷新 token 方法如下所示:

private async Task RefreshAccessToken()
    {


        var valuesRequest = new List<KeyValuePair<string, string>>();
        valuesRequest.Add(new KeyValuePair<string, string>("client_id", "*****"));
        valuesRequest.Add(new KeyValuePair<string, string>("client_secret","****"));
        valuesRequest.Add(new KeyValuePair<string, string>("grant_type", "refresh_token"));
        valuesRequest.Add(new KeyValuePair<string, string>("refresh_token", "*****"));


        RefreshTokenResponse refreshTokenResponse = null;

        var request = new HttpRequestMessage(HttpMethod.Post,
        "https://*****/connect/token");

        request.Content = new FormUrlEncodedContent(valuesRequest);

        var clientJAAPI = _httpClientFactory.CreateClient();
        var responseclientJAAPI = await clientJAAPI.SendAsync(request);

        if (responseclientJAAPI.IsSuccessStatusCode)
        {
            refreshTokenResponse = await responseclientJAAPI.Content.ReadAsAsync<RefreshTokenResponse>();

            //this updates the POCO object representing the configuration but not the appsettings.json :
            _appSettings.Value.Token = refreshTokenResponse.access_token;

        }

    }

请注意,我正在更新代表配置的 POCO 对象而不是 appsettings.json,因此新值存储在内存中。我想为后续请求更新 appsettings.json。

如果提出的解决方案需要在 Startup.ConfigureService 中定义 Httpclient 的主要设置,则需要允许创建 HttpClient 的不同实例,因为其中一个 HttpClient 实例(在另一种方法中使用以调用第二个 API ) 不需要 token 即可发送请求。

最佳答案

看起来你需要DelegatingHandler .简而言之,您可以“拦截”您的 http 请求并添加 Authorization header ,然后尝试执行它,如果 token 无效,请刷新 token 并再试一次。像这样的东西:

public class AuthenticationDelegatingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = await GetTokenAsync();
        request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
        var response = await base.SendAsync(request, cancellationToken);

        if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
        {
            token = await RefreshTokenAsync();
            request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
            response = await base.SendAsync(request, cancellationToken);
        }

        return response;
    }
}

您可以像这样在 Startup.cs 中注册此委托(delegate)处理程序:

services.AddTransient<AuthenticationDelegatingHandler>();
services.AddHttpClient("MySecuredClient", client =>
    {
        client.BaseAddress = new Uri("https://baseUrl.com/");
    })
    .AddHttpMessageHandler<AuthenticationDelegatingHandler>();

然后这样使用:

var securedClient = _httpClientFactory.CreateClient("MySecuredClient");
securedClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "v2/relativeUrl"));

关于在 appsetting.json 中存储刷新 token 。我认为这不是一个好主意,因为刷新 token 没有过期时间。如果您可以使用凭据首次获取新 token ,请使用它,然后将刷新 token 存储在内存中以供进一步刷新。

Here您可以看到我如何管理客户端凭证 token 刷新并尝试使其适用于您的场景。


更新:

Here您可以在 nuget 中找到相同但由专业人士实现的想法.用法很简单:

services.AddAccessTokenManagement(options =>
{
    options.Client.Clients.Add("identityserver", new ClientCredentialsTokenRequest
    {
        Address = "https://demo.identityserver.io/connect/token",
        ClientId = "m2m.short",
        ClientSecret = "secret",
        Scope = "api" // optional
    });
});

services.AddHttpClient<MyClient>(client =>
{
    client.BaseAddress = new Uri("https://demo.identityserver.io/api/");
})
.AddClientAccessTokenHandler();

MyClient 发送的请求将始终具有有效的不记名 token 。刷新自动执行。

关于c# - 如何使用 IHttpClientFactory 刷新 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56204350/

相关文章:

c# - 存储过程无法使用动态 sql 文本正确运行

javascript - 我需要一个先在 session 中保存变量然后打开新选项卡的链接

c# - 如何在服务层获取用户

c# - 如何检测 HttpClient.CancelPendingRequests 是否被用于取消挂起的请求?

c# - 在 ASP.NET 中使用异步操作时,如何消除 HTTP 请求之间的延迟?

c# - 为什么C#的HttpClient调用不了这个URL(总是超时)?

c# - 使用事件处理程序刷新所有 DataGrid

c# - 如何检测防病毒/防火墙是否阻止我的应用程序?

c# - 发布字符串请求

asp.net-core - 如何在 ASP.NET Core 中实现 IConfiguration 接口(interface)以供 Dapper 使用?