我正在尝试使用OAuth保护两个服务之间的通信(从现在开始称为使用者和提供者)。
让我们想象消费者刚刚起步。现在,多个http呼叫几乎同时到达。消费者需要与提供者通信以处理请求。我非常希望消费者为此通信重用单个令牌(而不是为每个传入请求获取新令牌)。首先,当令牌过期时,应该获取一个新令牌。
如何实现这一目标?
$ python3 bst.py
5
8
10
15
然而,我怀疑以上是要走的路。这种怀疑基于编译器优化等;见Eric Lippert的this post。
我正在尝试这样做,令牌可以被许多线程一次读取,但只能由一个更新。我也研究过ReaderWriterLockSlim,但这似乎没有帮助解决我的问题。 (请注意,由于我在GetTokenAsync中进行了异步调用,因此更加复杂。)
更新 基于@EricLippert的评论,我更新了代码:
public class TokenProvider
{
private readonly HttpClient _httpClient;
private Token _token;
private object _lock = new object();
public TokenProvider(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetTokenAsync()
{
if (_token != null && !_token.IsExpired())
{
return _token;
}
else
{
string oauthPostBody = string.Format(
"grant_type=client_credentials&client_id={0}&client_secret={1}", "fakeClientId", "fakeSecret");
var tokenEndpoint = ...;
var response = await _httpClient.PostAsync(tokenEndpoint.Uri, new StringContent(oauthPostBody));
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
lock (_lock)
{
if (_token == null || _token.IsExpired())
{
string expiresIn = jsonResponse.expires_in;
_token = new Token(jsonResponse.access_token, int.Parse(expiresIn));
}
return _token;
}
}
}
private class Token
{
private readonly string _token;
private readonly DateTime _expirationDateTime;
public Token(string token, int expiresIn)
{
_token = token;
_expirationDateTime = DateTime.UtcNow.AddSeconds(expiresIn);
}
public bool IsExpired()
{
return DateTime.UtcNow > _expirationDateTime;
}
public static implicit operator string(Token token)
{
return token._token;
}
}
}
我正在使用Stephen Cleary的AsyncReaderWriterLock。 这是一种更好的方法吗?或者我只是把自己挖进一个更大的洞?
答案 0 :(得分:2)
然而,我怀疑以上是要走的路。这种怀疑基于编译器优化等;看Eric Lippert的这篇文章
我们不必假设奇特的编译器优化。首先,明显的问题是令牌是否已过期更改:
if
。所以就是这样。我们无法保证返回的令牌有效。但它远不止于此。我们甚至不保证返回的令牌是当前令牌。
if
。此处存在TOCTOU问题,无论您实施双重检查锁定是否存在任何问题。那是检查时间不是使用时间。你对令牌的所有了解都是它在过去的某个时间没有过期,但所有令牌都是如此。