【发布时间】:2017-05-29 18:25:28
【问题描述】:
我正在尝试使用 OAuth(从现在开始称为消费者和提供者)保护两个服务之间的通信。
让我们假设消费者刚刚启动。现在多个 http 调用几乎同时到达它。消费者需要与提供者通信才能处理请求。我非常希望消费者为这种通信重用一个令牌(而不是为每个传入的请求获取一个新令牌)。首先,当令牌过期时,应该获取一个新令牌。
如何做到这一点?
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;
}
}
}
但是,我怀疑上述方法是否可行。这种怀疑是基于编译器优化等。请参阅 Eric Lippert 的 this post。
我正在尝试让多个线程同时读取令牌,但只能由单个线程更新。我也研究了 ReaderWriterLockSlim,但这似乎无助于解决我的问题。 (请注意,由于我在 GetTokenAsync 中有一个异步调用,因此它变得更加复杂。)
更新 根据@EricLippert 的评论,我更新了代码:
public class TokenProvider
{
private readonly HttpClient _httpClient;
private readonly IApplicationConfig _config;
private Token _token;
private AsyncReaderWriterLock _lock = new AsyncReaderWriterLock();
public TokenProvider(HttpClient httpClient, IApplicationConfig config)
{
_httpClient = httpClient;
_config = config;
}
public bool TryGetExistingToken(out string token)
{
using (_lock.ReaderLock())
{
if (_token != null)
{
token = _token;
return true;
}
else
{
token = null;
return false;
}
}
}
public async Task<string> GetNewTokenAsync()
{
using (await _lock.WriterLockAsync())
{
if (_token != null && !_token.IsExpired())
{
return _token;
}
else
{
var clientId = _config.Get<string>("oauth.clientId");
var secret = _config.Get<string>("oauth.sharedSecret");
string oauthPostBody = string.Format(
"grant_type=client_credentials&client_id={0}&client_secret={1}", clientId, secret);
var queueEndpoint = _config.GetUri("recommendationQueue.host");
var tokenPath = _config.Get<string>("recommendationQueue.path.token");
var tokenEndpoint = new UriBuilder(queueEndpoint) {Path = tokenPath};
var response = await _httpClient.PostAsync(tokenEndpoint.Uri, new StringContent(oauthPostBody));
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (_token == null || _token.IsExpired())
{
string expiresIn = jsonResponse.expires_in;
string accessToken = jsonResponse.access_token;
_token = new Token(accessToken, 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。 这是更好的方法吗?还是我只是把自己挖到了一个更大的坑里?
【问题讨论】:
-
我觉得有点奇怪的是,您可以拥有 20 个不同的令牌提供者,每个提供者都有自己的客户端,并且它们都可以返回相同的令牌。这不是让你感到深深的破碎吗?令牌提供者不应该提供一个令牌来自给定客户端 ???
-
你完全正确!我已经更改了代码,使得 _token 和 _lock 现在是实例字段。
-
然后我确保在引导应用程序时,我只有一个 TokenProvider 实例。
-
我不明白的是,这些方法中的任何一种保证是什么。显然不是返回了有效的令牌。你能说的最好的就是将努力返回一个有效的令牌。令牌是如何使用的?您是否在循环中尝试获取有效令牌并在检查时间和使用时间之间证明它无效时重试?
-
另外,请考虑以下场景。线程 A 获取锁 X。我们切换到线程 B。线程 B 获取写入器锁,然后在持有锁的同时等待。所以我们回到试图做其他工作的调用者。该工作导致线程 B 尝试获取锁 X,因此 B 阻塞,我们切换回线程 A。现在线程 A 尝试获取写入器锁并阻塞。两个线程现在都被阻塞了,锁永远不会被释放。是什么阻止了您的程序中出现这种情况?
标签: .net multithreading oauth locking token