使用静态HttpClient刷新令牌 [英] Refresh Token using Static HttpClient

查看:58
本文介绍了使用静态HttpClient刷新令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用VS 2017 .Net 4.5.2

Using VS 2017 .Net 4.5.2

我有以下课程

public static class MyHttpClient
{
    //fields
    private static Lazy<Task<HttpClient>> _Client = new Lazy<Task<HttpClient>>(async () =>
    {
        var client = new HttpClient();
        await InitClient(client).ConfigureAwait(false);
        return client;
    });

    //properties
    public static Task<HttpClient> ClientTask => _Client.Value;

    //methods
    private static async Task InitClient(HttpClient client)
    {
        //resey headers
        client.DefaultRequestHeaders.Clear();
        //Set base URL, NOT thread safe, which is why this method is only accessed via lazy initialization
        client.BaseAddress = new Uri(ConfigurationManager.AppSettings["baseAddress"]);//TODO: get from web.config? File? DB?
        //create new request to obtain auth token
        var request = new HttpRequestMessage(HttpMethod.Post, "/ouath2/token"); //TODO: get from web.config? File? DB? prob consts
        //Encode secret and ID 
        var byteArray = new UTF8Encoding().GetBytes($"{ConfigurationManager.AppSettings["ClientId"]}:{ConfigurationManager.AppSettings["ClientSecret"]}");
        //Form data
        var formData = new List<KeyValuePair<string, string>>();
        formData.Add(new KeyValuePair<string, string>("grant_type", "refresh_token"));
        formData.Add(new KeyValuePair<string, string>("refresh_token", ConfigurationManager.AppSettings["RefreshToken"]));
        //set content and headers
        request.Content = new FormUrlEncodedContent(formData);
        request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
        //make request
        var result = await HttpPost(request, client).ConfigureAwait(false);
        //set bearer token
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (string)result.access_token);

        //TODO: error handle
    }

    private static async Task<dynamic> HttpPost(HttpRequestMessage formData, HttpClient client)
    {
        using (var response = await client.SendAsync(formData).ConfigureAwait(false))
        {
            response.EnsureSuccessStatusCode();//TODO: handle this

            return await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
        }

    }
}

仍然

如果在应用程序生命周期中只需要提取令牌一次,那么这很好用,但是我正在使用的API短期持有者令牌(15分钟)。

This works fine if the token only needs to be fetched once in an applications life, however the API I'm talking to uses short lived bearer tokens (15mins).

由于我将HttpClient用作要重用的静态变量,因此我无法更改Default请求标头,因为它们不是线程安全的。但是我需要每15分钟请求一个Bearer令牌。

As I'm using HttpClient as a static to be reused, I cannot change the Default request headers as they are not threadsafe. But I'm required to request a Bearer token every 15mins.

在这种特殊情况下,我如何获得一个新的Bearer令牌并设置默认标头?

How would I achieve obtaining a new bearer token and setting the default header in this particular scenario?

推荐答案

更新:添加了SemaphoreSlim以锁定刷新事务

免责声明:未经测试,可能需要进行一些调整

注1:信号灯需要放在一个try / catch / finaly块以确保在引发错误时释放。

注释2:此版本将排队刷新令牌调用,如果负载很高,则会降低性能。要解决此问题;使用布尔指示器检查刷新是否发生。例如,这可能是静态布尔值。

note 2: this version will queue the refresh token calls which significant degrades performance if load is high. To fix this; use a bool indicator to check if the refresh is occurred. This might be a static bool for example

目标是仅在需要时使用刷新令牌。固定的时间间隔不会对您有帮助,因为有一天该时间间隔可能会改变。解决此问题的正确方法是重试是否出现403。

The goal is to only use the refresh token if needed. A fixed interval won't help you because, one day, this interval might change. The correct way to handle this is to retry if a 403 occurs.

您可以使用HttpClientHandler与HttpClient一起使用。

You can use a HttpClientHandler to work with your HttpClient.

重写SendAsync,以处理并重试403。

Override the SendAsync, to handle and retry the 403.

要使其正常工作,您需要此 httpclient的构造器

For this to work you'll need this constructor of httpclient:

从我(半)脑袋的顶部开始,一定是这样的:

From the top of my (semi) head, it must be something like this:

HttpMessageHandler

public class MyHttpMessageHandler : HttpMessageHandler
{
    private static SemaphoreSlim sem = new SemaphoreSlim(1);

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {  
    var response = await base.SendAsync(request, cancellationToken);

    //test for 403 and actual bearer token in initial request
    if (response.StatusCode == HttpStatusCode.Unauthorized &&
        request.Headers.Where(c => c.Key == "Authorization")
                .Select(c => c.Value)
                .Any(c => c.Any(p => p.StartsWith("Bearer"))))
        {

            //going to request refresh token: enter or start wait
            await sem.WaitAsync();

            //some typical stuff
            var pairs = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("grant_type", "refresh_token"),
                new KeyValuePair<string, string>("refresh_token", yourRefreshToken),
                new KeyValuePair<string, string>("client_id", yourApplicationId),
            };

            //retry do to token request
            using ( var refreshResponse = await base.SendAsync(
                new HttpRequestMessage(HttpMethod.Post, 
                   new Uri(new Uri(Host), "Token")) 
                   { 
                      Content = new FormUrlEncodedContent(pairs) 
                   }, cancellationToken))
            {
                var rawResponse = await refreshResponse.Content.ReadAsStringAsync();
                var x = JsonConvert.DeserializeObject<RefreshToken>(rawResponse);

                //new tokens here!
                //x.access_token;
                //x.refresh_token;

                //to be sure
                request.Headers.Remove("Authorization");
                request.Headers.Add("Authorization", "Bearer " + x.access_token);

                //headers are set, so release:
                sem.Release();  

                //retry actual request with new tokens
                response = await base.SendAsync(request, cancellationToken);

            }
        }

        return response;
    }
}
}

发送示例,

public async Task<int> RegisterAsync(Model model)
{
    var response = await YourHttpClient
       .SendAsync(new HttpRequestMessage(HttpMethod.Post, new Uri(BaseUri, "api/Foo/Faa"))
    {  
        Content = new StringContent(
           JsonConvert.SerializeObject(model),
           Encoding.UTF8, "application/json")
    });

    var result = await response.Content.ReadAsStringAsync();
    return 0;
}

这篇关于使用静态HttpClient刷新令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆