在另一个 Web API 中管理 Web API JWT 令牌的最佳实践

Best practices for managing Web API JWT token in another Web API

我的项目:

Web API 项目 - ASP .NET Framework 4.8

有问题吗?

代码流程如下:

1.) API 被调用 -> 它必须调用另一个 API -> 2.) 获取 JWT 身份验证令牌 -> 3.) 调用所需的方法。

问题是,如果我的 API 被调用 100 次,我将对 GetJwtToken() 方法调用 100 次,对所需方法本身调用 100 次,这似乎是 auth 的开销服务器。令牌本身的生命周期为 2 小时。

是否有关于如何在另一个 Web API 中管理 Web API JWT 令牌的最佳实践记录?

我尝试了什么?

我已经尝试了以下解决方案,但我仍然不确定它们是否可以被视为良好做法。

if (response.StatusCode == HttpStatusCode.Unauthorized)

如果我们进入 if 子句,我们通过调用 catch 块内的 GetJwtToken() 方法更新 Token 属性,然后调用再次递归该方法。这样,我们仅在令牌过期并抛出未经授权的异常时才更新令牌。


是否有任何其他方法可以在另一个 Web API 中管理 Web API 的 JWT 令牌,什么是最佳实践?
一些代码片段、伪代码或文章将不胜感激。


编辑 1:
我读过 this 问题,但它对我没有帮助,因为它是关于如何在前端部分管理令牌的。这里的项目是WebAPI都是服务端的
编辑 2:
在这里和那里编辑了一些句子,使其更具可读性。
编辑 3:
又加了一个我想到的选项。

也许考虑你 API 保存 AuthToken(有状态)。

我对你目前的流程感到困惑,通常你会有一个 AuthApi 具有提供 AuthorizationTokens.

的功能

一旦调用者有了 AuthToken 它就应该保存它;如您所知,对于 front-end,考虑本地存储、会话存储和 cookie,对于后端或 API,您可以考虑有状态的 API,它将令牌保存在全局状态,因此它可以将它附加到每个请求,而无需使用您的 AuthApi back-and-forth (它会在令牌过期时进行一次旅行,等等)。

我会以某种方式处理这个 BaseApiService

public class BaseApiService
{
    private readonly IHttpClientFactory httpClientFactory;
    private readonly ITokenHandler tokenHandler;

    public BaseApiService(IHttpClientFactory httpClientFactory, ITokenHandler tokenHandler)
    {
        this.httpClientFactory = httpClientFactory;
        this.tokenHandler = tokenHandler;
    }

    protected async Task<HttpResponseMessage> RequestAsync(HttpRequestMessage requestMessage)
    {
        var httpClient = httpClientFactory.CreateClient();
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenHandler.Token);
        var response = await httpClient.SendAsync(requestMessage);
        
        if (!response.IsSuccessStatusCode)
        {
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                var token = await tokenHandler.UpdateTokenAsync();
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
                return await RequestAsync(requestMessage);
            }
        }

        return response;
    }
}

它将负责发出请求、响应序列化(请注意,为了简单起见,我使用了字符串响应)和处理每个请求的令牌。此外,您可能还需要考虑处理错误并处理无限循环,因为它当前正在调用自己(例如,在第二次调用时,如果它再次未经授权,则错误退出)。

令牌处理程序将在 DI 中定义为单例,这是实现

public interface ITokenHandler
{
    string Token { get; }
    Task<string> UpdateTokenAsync();
}

public class TokenHandler : ITokenHandler
{
    private readonly IHttpClientFactory httpClientFactory;
    public string Token { get; private set; } 

    public TokenHandler(IHttpClientFactory httpClientFactory)
    {
        this.httpClientFactory = httpClientFactory;
    }
    
    public async Task<string> UpdateTokenAsync()
    {
        var httpClient = httpClientFactory.CreateClient();
        var result = await httpClient.PostAsync("/external-api/token", new FormUrlEncodedContent(new []
        {
            new KeyValuePair<string, string>("username", "external-admin"),
            new KeyValuePair<string, string>("password", "external-password"),
        }));

        // or handle it however you want
        var token = result.IsSuccessStatusCode
            ? await result.Content.ReadAsStringAsync()
            : null;

        if (!String.IsNullOrEmpty(token))
        {
            Token = token;
        }

        return Token;
    }
}

这就是您消费 BaseApiService

的方式
public class TodoService : BaseApiService
{
        public TodoService(IHttpClientFactory httpClientFactory, ITokenHandler tokenHandler) 
        : base(httpClientFactory, tokenHandler)
    {
    }

    public async Task<string> GetTodoAsync(int id)
    {
        var response = await RequestAsync(new HttpRequestMessage(HttpMethod.Get, $"/todo/{id}"));
        return await response.Content.ReadAsStringAsync();
    }
}

我认为您不需要添加任何 ValidTo 逻辑,只需依赖来自第 3 方 API 的 Unauthorized 响应,因为您只会使代码复杂化无论如何你都必须处理 Unauthorized 个回复。

唯一的问题是您可能 lock getting/setting 来自 TokenHandler 的令牌,但这只是一个基本示例,用于说明我将如何实现它。

由于字数限制,我会扩大我的评论来回答。

首先,re-consider / re-examine为什么每次API调用都需要调用auth服务器?您是否有某种数据存储,例如数据库、缓存(在内存中或远程)、Azure blob 存储或共享文件夹?如果有,您可以考虑将您的访问令牌保存到您选择的数据存储中。

现在,让我们来处理令牌过期时间。取决于外部 API 如何授予访问令牌(我假设这里是 OAuth2),您通常可以访问令牌的到期时间,例如使用 expires_in in the responseexpires_in 等于自 unix 纪元以来的秒数,因此您应该知道令牌何时过期。然后,您可以保存授予数据存储的令牌及其过期时间和刷新令牌。 当您使用缓存时,您可以将缓存条目设置为在其中的令牌过期之前过期分钟。

当您接到下一个 API 电话时,请检查您的数据存储中是否有“有效”令牌。如果否,调用以获取新的 JWT 令牌并使用上述方法持久化它。否则,请尝试使用数据存储中的令牌进行 API 调用。如果您有后台服务,例如 WebJob 或 Hangfire,您可以定期根据令牌验证端点验证所有令牌(如果您的外部 API 提供一个)并在需要时刷新它们。

您应该始终处理未经授权的响应。令牌可以在到期前被撤销。如果您收到未经授权的响应,您可以尝试 re-authenticate 与外部 API 并刷新保存在数据存储中的令牌。如果令牌生成需要让用户参与,您可以 return 401 给您的客户。

最后,您还需要考虑安全性。当您持久化令牌时,即使是您自己的数据存储,您也需要对它们进行加密。 This 适用于 ASP.NET 核心,但仍然值得一读并在 API.

中做类似的事情