服务重用令牌服务通信
我试图使用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。 这是一个更好的方法吗?还是我只是挖掘到一个更大的洞?
但是,我有我的怀疑,以上是要走的路。这种怀疑主要基于编译器优化;看到这篇文章由埃里克Lippert
我们不必假设异国情调的编译器优化。首先,有明显的问题,即是否令牌过期变化:
- 没有在静态变量的未过期的令牌。
- 线程检测到存在未过期的令牌并输入
if
。 - 线程A挂起。
- 线程B运行的时间足够长,令牌过期。
- 线程恢复并返回过期的令牌。
所以就是这样。我们不保证返回的令牌有效。但比这更糟糕。我们甚至没有保证返回的令牌是当前令牌。
- 静态变量中有一个未过期的标记。
- 线程检测到存在未过期的令牌并输入
if
。 - 线程A将未过期的标记放在评估栈上作为返回。
- 线程A挂起。
- 线程B运行的时间足够长,令牌过期。
- 线程C运行,检测到令牌已过期,并用不同的令牌替换它。
- 线程恢复并返回一个甚至不是变量当前内容的过期令牌。
这里有个TOCTOU问题,不管你实现双重检查锁定有什么问题。那是检查时间不是使用时间。你所知道的令牌是它在过去的的某个时间没有过期,但所有令牌都是如此。
非常感谢非常有帮助的评论 - 让我意识到我面临的问题的正式名称。 我很困惑,我似乎无法找到任何人描述如何处理具体问题(我想很多人会想重用令牌服务来通信服务)。 你有没有机会知道任何资源? – SabrinaMH
我发现有点奇怪,你可以有20个不同的令牌提供者,每个都有自己的客户端,他们都可以返回相同的标记。这不会让你深深伤心吗?令牌提供者是否应该提供给定客户端的令牌*? –
你是对的!我已经更改了代码,使_token和_lock现在是实例字段。 – SabrinaMH
然后我确定,在引导应用程序时,我只有一个TokenProvider实例。 – SabrinaMH