如何在API之间的客户端凭据流中存储和管理令牌?

问题描述

我不知道如何为两个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/

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...