在我的一个应用程序中,我已与 Infusionsoft 集成,并且访问令牌会在特定时间后过期。
现在前端发出多个请求以获取不同的数据。当令牌过期时,它会刷新令牌并获取新的访问和刷新令牌。但是在我获得新的访问和刷新令牌之前,来自 UI 的后续请求会尝试使用旧的刷新令牌刷新令牌,它们都会导致错误。
克服这个问题的最佳方法是什么?
在我的一个应用程序中,我已与 Infusionsoft 集成,并且访问令牌会在特定时间后过期。
现在前端发出多个请求以获取不同的数据。当令牌过期时,它会刷新令牌并获取新的访问和刷新令牌。但是在我获得新的访问和刷新令牌之前,来自 UI 的后续请求会尝试使用旧的刷新令牌刷新令牌,它们都会导致错误。
克服这个问题的最佳方法是什么?
(我的回答并非针对 Infusionsoft)。
这是我用来防止重复并发请求以更新/刷新不记名令牌的方法,当 Web 服务客户端可能发出使用相同不记名令牌的并发请求时(以防止每个请求线程或异步上下文创建自己的单独刷新请求)。
诀窍是使用在 a 内部交换的缓存Task<AuthResponseDto>
(其中包含最新成功获得AuthResponseDto
的 DTO 类型) (您不能在 a 内部,但您可以在 a 内部复制引用,然后在 外部复制)。access_token
lock
await
lock
Task
lock
await
lock
// 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
}
}
}
}
有一个教程讨论了按计划刷新令牌。