我試圖使用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現在是實例字段。 – SabrinaMH
然後我確定,在引導應用程序時,我只有一個TokenProvider實例。 – SabrinaMH