24

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

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

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

我已经实现了一些简单的工作,但我正在寻找一种更优雅的解决方案,可以在当前令牌过期时动态更新标头:

我已经在 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;

        }

    }

refresh 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 的主要设置,则需要允许创建不同的 HttpClien 实例,因为其中一个 HttpClient 实例(在另一种方法中用于调用第二个 API)没有t 需要令牌来发送请求。

4

1 回答 1

52

看起来你需要DelegatingHandler。简而言之,您可以“拦截”您的 http 请求并添加 Authorization 标头,然后尝试执行它,如果令牌无效,请刷新令牌并再试一次。就像是:

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 中注册此委托处理程序,如下所示:

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 中存储刷新令牌。我认为这不是一个好主意,因为刷新令牌没有到期时间。如果您可以使用凭据首次获取新令牌,请使用它,然后将刷新令牌存储在内存中以供进一步刷新。

在这里,您可以看到我如何管理客户端凭据令牌刷新并尝试使其适用于您的场景。


更新:

在这里,您可以找到相同的想法,但由专业人员实施并在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将始终具有有效的不记名令牌。刷新自动执行。

于 2019-05-19T05:10:32.340 回答