我有一个DelegatingHandler
,它在单例HttpClient
的构造函数中传递。
此处理程序负责进行基本身份验证,以获取承载令牌,该令牌将用于后续请求,直到令牌过期。令牌过期后,将再次触发基本身份验证,以刷新令牌,依此类推。
public class MyMessageHandler : DelegatingHandler
{
private readonly string baseAddress;
private readonly string user;
private readonly string pass;
private readonly SemaphoreSlim sem;
private Token token;
public MyMessageHandler() : base()
{
// validation/assignment of baseAddress, userName, password
// ..omitted for brevity
sem = new SemaphoreSlim(1);
// this is the first time, so get the token
token = GetTokenAsync().GetAwaiter().GetResult();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("Authorization"))
{
request.Headers.Add("Authorization", $"Bearer {token.AccessToken}");
}
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
// if a token refresh is needed
if (response.StatusCode == HttpStatusCode.Unauthorized && whateverOtherCheckToTriggerTokenRefresh
{
try
{
// don't want multiple requests refreshing the token
await sem.WaitAsync().ConfigureAwait(false);
token = await GetTokenAsync().ConfigureAwait(false);
// we have the token, now set the headers to the new values
request.Headers.Remove("Authorization");
request.Headers.Add("Authorization", $"Bearer {token.AccessToken}");
// replay the request with the new token
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
}
finally
{
sem.Release();
}
}
return response;
}
private async Task<Token> GetTokenAsync()
{
var authBytes = Encoding.UTF8.GetBytes($"{user}:{pass}");
var basicAuthToken = Convert.ToBase64String(authBytes);
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
// get ourselves a token using basic auth
var message = new HttpRequestMessage(HttpMethod.Post, new Uri(new Uri(baseAddress), "/token"))
{
Content = new FormUrlEncodedContent(pairs)
};
message.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuthToken);
var response = await base.SendAsync(message, new CancellationToken()).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// return our token
return JsonConvert.DeserializeObject<Token>(result);
}
}
我在刷新/更新令牌步骤中使用了Semaphore
,因为这是一个并发的WCF应用程序,并且我不希望多个请求都请求一个新令牌。刷新步骤完成后,token
的{{1}}字段将设置为MyMessageHandler
返回的新Token
对象,并释放信号量,因此其他等待的请求将进入代码块。
1)现在如何防止在GetTokenAsync()
行上等待的请求自己执行刷新令牌步骤?
2)我是否应该担心传入请求在信号灯中被更新时试图抢占await sem.WaitAsync().ConfigureAwait(false);
字段的值?如果是这样,我应该在获取新令牌后立即执行token
之类的事情吗?
更新:
在Damien_The_Unbeliever的答案之后,这是我实现他的答案的方法。我仍然很难理解如何实现答案。
Interlocked.Exchange(ref token, newlyFetchedToken);
答案 0 :(得分:2)
不考虑其他一些问题(空catch
块以及单个HttpRequestMessage
实例实际上可以被发送多次的假设),我通常在字段中遇到的是{ {1}}。
在传出请求中,将此字段的副本抓取到局部变量中,然后Task<Token>
实际标记。如果您收到未经授权的回复,请创建一个新的await
并执行一个TaskCompletionSource
以交换该字段中的InterlockedCompareExchange
。如果交换成功,则现在是您的责任来续订令牌并完成Task
。
但是,如果Task
失败,则意味着其他人已经或正在替换令牌。循环回到您的方法顶部,并InterlockedCompareExchange
来代替这个新的await
。
没有信号灯,很简单的行为就可以推断出来。甚至在您尝试使用新Task<Token>
时,它也可能已经过期-因此,请准备多次循环并制定一些策略,以便在出现其他情况时不会永远循环玩,令牌没有真正的问题。
并删除空的Token
。