1

在我的一个应用程序中,我已与 Infusionsoft 集成,并且访问令牌会在特定时间后过期。

现在前端发出多个请求以获取不同的数据。当令牌过期时,它会刷新令牌并获取新的访问和刷新令牌。但是在我获得新的访问和刷新令牌之前,来自 UI 的后续请求会尝试使用旧的刷新令牌刷新令牌,它们都会导致错误。

克服这个问题的最佳方法是什么?

4

2 回答 2

0

(我的回答并非针对 Infusionsoft)。

这是我用来防止重复并发请求以更新/刷新不记名令牌的方法,当 Web 服务客户端可能发出使用相同不记名令牌的并发请求时(以防止每个请求线程或异步上下文创建自己的单独刷新请求)。

诀窍是使用在 a 内部交换的缓存Task<AuthResponseDto>(其中包含最新成功获得AuthResponseDto的 DTO 类型) (您不能在 a 内部,但您可以在 a 内部复制引用,然后在 外部复制)。access_tokenlockawaitlockTasklockawaitlock

// NOTE: `ConfigureAwait(false)` calls omitted for brevity. You should re-add them back.

class MyHttpClientWrapper
{
    private readonly String refreshTokenOrClientCredentialsOrWhatever;

    private readonly IHttpClientFactory hcf;

    private readonly Object lastAuthTaskLock = new Object();

    private Task<AuthResponseDto> lastAuthTask;
    private DateTime lastAuthTaskAt;

    public MyHttpClientWrapper( IHttpClientFactory hcf )
    {
        this.hcf = hcf ?? throw new ArgumentNullException( nameof(hcf) );

        this.refreshTokenOrClientCredentialsOrWhatever = LoadFromSavedConfig();
    }

    private async Task<AuthResponseDto> RefreshBearerTokenAsync()
    {
        using( HttpClient hc = this.hcf.CreateClient() )
        using( HttpResponseMessage resp = await hc.PostAsync( this.refreshTokenOrClientCredentialsOrWhatever ) )
        {
            AuthResponseDto ar = await DeserializeJsonResponseAsync( resp );
            this.lastAuthTaskExpiresAt = DateTime.UtcNow.Add( ar.MaxAge );
            return ar;
        }
    }

    private async Task<String> RefreshBearerTokenIfNecessaryAsync()
    {
        Task<AuthResponseDto> task;

        lock( this.lastAuthTaskLock )
        {
            if( this.lastAuthTask is null )
            {
                // e.g. This is the first ever request.

                task = this.lastAuthTask = this.RefreshBearerTokenAsync();
            }
            else
            {
                task = this.lastAuthTask;
                
                // Is the task currently active? If it's currently busy then just await it (thus preventing duplicate requests!)
                if( task.IsCompleted )
                {
                    // If the current bearer-token is definitely expired, then replace it:
                    if( this.lastAuthTaskExpiresAt <= DateTime.UtcNow )
                    {
                        task = this.lastAuthTask = this.RefreshBearerTokenAsync();
                    }
                }
                else
                {
                    // Continue below.
                }
            }
        }

        AuthResponseDto ar = await task;
        return ar.BearerToken;
    }

    //

    public async Task<CustomerDto> GetCustomerAsync( Int32 customerId )
    {
        // Always do this in every request to ensure you have the latest bearerToken:
        String bearerToken = await this.RefreshBearerTokenIfNecessaryAsync();
        
        using( HttpClient hc = this.hcf.Create() )
        using( HttpRequestMessage req = new HttpRequestMessage() )
        {
            req.Headers.Add( "Authorization", "Bearer " + bearerToken );
            using( HttpResponseMessage resp = await hc.SendAsync( req ) )
            {
                if( resp.StatusCode == 401 )
                {
                    // Authentication error before the token expired - invoke and await `RefreshBearerTokenAsync` (rather than `RefreshBearerTokenIfNecessaryAsync`) and see what happens. If it succeeds then re-run `req`) otherwise throw/fail because that's an unrecoverable error.
                }

                // etc
            }
        }
    }
}
于 2020-09-03T05:49:38.160 回答
-1

有一个教程讨论了按计划刷新令牌。

https://developer.infusionsoft.com/tutorials/making-oauth-requests-without-user-authorization/#as-you-go-refresh-the-access-token

于 2016-09-20T21:24:05.317 回答