问题描述
我不知道如何为两个API实现Client Credential Flow的客户端部分。
场景: API A从身份服务器(keycloak)获取JWT令牌(您自己的令牌)以访问API B。
技术:带有C#的.NET Core
如何在API A中存储令牌以用于API B的请求? 如何管理令牌到期以使其刷新?
使用存储令牌并使用Timer刷新令牌的类的单例实例是一个好选择吗? 拥有工作人员服务(后台)来管理令牌到期更好吗?
在这种情况下,还有其他方法可以存储和管理令牌到期吗?
是否可以将IdentityModel
与密钥斗篷一起使用?
解决方法
您可以使用类似于以下内容的单例:
public class TokenStore
{
public string Token { get; set; }
public string RefreshToken { get; set; }
}
接下来,您可以创建一个System.Net.Http.DelegatingHandler
来处理整个令牌过程。会是这样的:
public class MyTokenHandler : DelegatingHandler
{
private readonly TokenStore _tokenStore;
private TaskCompletionSource<object> updateTokenTaskCompletionSource;
public MyTokenHandler(TokenStore tokenStore)
{
_tokenStore = tokenStore;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
{
// check if token is already being fetched
if (updateTokenTaskCompletionSource != null)
await updateTokenTaskCompletionSource.Task;
var httpResponse = await InternalSendAsync(request,cancellationToken);
if (httpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized)
// you can add conditions such as excluding Paths and checking response message to avoid recursion
// you can also verify token expiary based on time
{
// intentionally not passing in the refresh token
// at this point we know there is an expired token. So,we can update token regardless of the main request being cancelled
await UpdateTokenAsync();
httpResponse = await InternalSendAsync(request,cancellationToken);
}
return httpResponse;
}
private async Task UpdateTokenAsync(CancellationToken cancellationToken = default)
{
// taskCompletionSource handles multiple requests attempting to refresh token at the same time
if (updateTokenTaskCompletionSource is null)
{
updateTokenTaskCompletionSource = new TaskCompletionSource<object>();
try
{
var refreshRequest = new HttpRequestMessage(HttpMethod.Post,"/token");
var refreshResponse = await base.SendAsync(refreshRequest,cancellationToken);
_tokenStore.Token = "updated token here";
}
catch (Exception e)
{
updateTokenTaskCompletionSource.TrySetException(e);
updateTokenTaskCompletionSource = null;
throw new Exception("Failed fetching token",e);
}
updateTokenTaskCompletionSource.TrySetResult(null);
updateTokenTaskCompletionSource = null;
}
else
await updateTokenTaskCompletionSource.Task;
}
private async Task<HttpResponseMessage> InternalSendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer",_tokenStore.Token);
return await base.SendAsync(request,cancellationToken);
}
}
最后,在您的startup.cs中,您可以添加以下代码:
services.AddHttpClient<MyClientService>(httpClient =>
{
httpClient.BaseAddress = new Uri("Your base URL");
}).AddHttpMessageHandler<MyTokenHandler>();
请注意,我没有考虑设置RefreshToken
。您可以根据需要进行设置。这是整个示例项目:https://github.com/neville-nazerane/httpclient-refresh/