在另一个 Web API 中管理 Web API JWT 令牌的最佳实践
Posted
技术标签:
【中文标题】在另一个 Web API 中管理 Web API JWT 令牌的最佳实践【英文标题】:Best practices for managing Web API JWT token in another Web API 【发布时间】:2021-01-11 17:28:51 【问题描述】:我的项目:
Web API 项目 - ASP .NET Framework 4.8
有问题?
代码流程如下:
1.) API 被调用 -> 它必须调用另一个 API -> 2.) 获取 JWT 身份验证令牌 -> 3.) 调用所需的方法。
问题是,如果我的 API 被调用 100 次,我将对 GetJwtToken()
方法进行 100 次调用,并为所需的方法本身调用另外 100 次,这似乎是身份验证服务器的开销。令牌本身的寿命为 2 小时。
是否有关于如何记录?
我尝试了什么?
我已经尝试了以下解决方案,但我仍然不确定它们是否可以被视为良好做法。
一个具有两个静态属性Token
和ValidTo
的静态类和一个更新这些属性的静态方法GetJwtToken()
。在每次调用所需的外部 API 方法之前,我们会检查 ValidTo
属性并通过静态方法更新 Token
的值(如果它已过期)。
在我们的服务中,我们有一个静态私有字段Token
。调用外部API方法的方法被try
catch
块包围。如果令牌已过期,Catch(WebException ex)
预计会出现未经授权的异常。我检查 HTTP 状态代码 401 - 未经授权。
if (response.StatusCode == HttpStatusCode.Unauthorized)
如果我们进入 if
子句,我们会通过在 catch
块中调用 GetJwtToken()
方法来更新 Token
属性,然后再次递归调用该方法。这样,我们只有在令牌过期并且抛出未经授权的异常时才更新令牌。
ActionFilterAttribute
覆盖 OnActionExecuting(HttpActionContext actionContext)
方法。在我们进入 Web API 控制器之前,action 属性已经检查了我们是否有Token
以及它是否已经过期。这里的问题是我不确定在哪里保存Token
属性。可能作为另一个类中的静态值。
还有其他方法可以在另一个 Web API 中管理 Web API 的 JWT 令牌吗?什么是最佳实践? 一些代码 sn-ps、伪代码或文章将不胜感激。
编辑1: 我已经阅读了this 的问题,但这对我没有帮助,因为它是关于如何管理前端部分的令牌。这里的项目是 Web API,它都在服务器端。 编辑2: 在这里和那里编辑了一些句子,因此更具可读性。 编辑3: 添加了一个我想到的选项。
【问题讨论】:
【参考方案1】:也许考虑使用 API 来保存 AuthToken
(有状态)。
我对您当前的流程感到困惑,通常您会有一个AuthApi
,它具有提供AuthorizationTokens
的功能。
一旦调用者拥有AuthToken
,它应该保存它;如您所知,对于前端,考虑本地存储、会话存储和 cookie,对于后端或 API,您可以考虑使用有状态 API 将令牌保存为全局状态,因此它可以将其附加到每个请求而无需执行与您的AuthApi
来回切换(不过,它会在令牌过期时进行一次旅行,等等)。
【讨论】:
有状态 API 是什么意思?我的项目是 .Net Framework 4.8 Web API。如果我将它保存在某处的“状态”中,那对我有什么帮助?整个代码流是什么?令牌的有效期为 2 小时,因此在此之后,它将开始引发授权错误。您建议的事情就像我使用静态类的第一个解决方案,不是吗?【参考方案2】:我会用某种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;
它将负责发出请求、响应序列化(注意,为了简单起见,我使用了字符串响应)和处理每个请求的令牌。此外,您可能需要考虑处理错误并处理无限循环,因为它当前正在调用 self(例如,在第二次调用时,如果再次未经授权,则退出并出错)。
令牌处理程序将在 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
逻辑,而只需依赖来自 3rd 方 API 的 Unauthorized
响应,因为您只会使代码复杂化并且您必须处理 Unauthorized
无论如何都要回复。
唯一的问题是您可以通过lock
从TokenHandler
获取/设置令牌,但这只是一个基本示例,展示了我将如何实现它的想法。
【讨论】:
【参考方案3】:由于字数限制,我会扩展我的 cmets 来回答。
首先,重新考虑/重新检查为什么需要为每个 API 调用调用身份验证服务器?您是否有某种数据存储,例如数据库、缓存(内存或远程)、Azure Blob 存储或共享文件夹?如果有,您可以考虑将访问令牌持久保存到您选择的数据存储中。
现在,让我们处理令牌过期时间。取决于外部 API 如何授予访问令牌(我假设这里是 OAuth2),您通常可以访问令牌的到期时间,例如使用 expires_in
in the response。 expires_in
等于自 unix 纪元以来的秒数,因此您应该知道令牌何时到期。然后,您可以保存授予数据存储的令牌及其到期时间和刷新令牌。 当您使用缓存时,您可以将缓存条目设置为在其中的令牌过期前分钟过期。
当您收到下一次 API 调用时,请检查您的数据存储中是否有“有效”令牌。如果否,则调用以获取新的 JWT 令牌并使用上述方法将其持久化。否则,请尝试使用数据存储中的令牌进行 API 调用。如果您有后台服务,例如 WebJob 或 Hangfire,您可以定期根据令牌验证端点(如果您的外部 API 提供)验证所有令牌,并在需要时刷新它们。
您应该始终处理未经授权的回复。令牌可以在过期之前被撤销。如果您收到未经授权的响应,您可以尝试使用外部 API 重新进行身份验证并刷新保存在数据存储中的令牌。如果令牌生成需要用户参与,您可以将 401 返回给您的客户端。
最后,您还需要考虑安全性。当您持久保存令牌时,即使是保存到您自己的数据存储中,您也需要对它们进行加密。 This 适用于 ASP.NET Core,但仍然值得一读并在您的 API 中执行类似的操作。
【讨论】:
感谢您将 cmets 扩展为答案。 感谢您的回答,非常感谢!以上是关于在另一个 Web API 中管理 Web API JWT 令牌的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core Web API中使用Swagger