在另一个 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 令牌的最佳实践记录?
我尝试了什么?
我已经尝试了以下解决方案,但我仍然不确定它们是否可以被视为良好做法。
- 一个静态 class 具有两个静态属性
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
属性。可能作为另一个 class. 中的静态值
是否有任何其他方法可以在另一个 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 response。 expires_in
等于自 unix 纪元以来的秒数,因此您应该知道令牌何时过期。然后,您可以保存授予数据存储的令牌及其过期时间和刷新令牌。 当您使用缓存时,您可以将缓存条目设置为在其中的令牌过期之前过期分钟。
当您接到下一个 API 电话时,请检查您的数据存储中是否有“有效”令牌。如果否,调用以获取新的 JWT 令牌并使用上述方法持久化它。否则,请尝试使用数据存储中的令牌进行 API 调用。如果您有后台服务,例如 WebJob 或 Hangfire,您可以定期根据令牌验证端点验证所有令牌(如果您的外部 API 提供一个)并在需要时刷新它们。
您应该始终处理未经授权的响应。令牌可以在到期前被撤销。如果您收到未经授权的响应,您可以尝试 re-authenticate 与外部 API 并刷新保存在数据存储中的令牌。如果令牌生成需要让用户参与,您可以 return 401 给您的客户。
最后,您还需要考虑安全性。当您持久化令牌时,即使是您自己的数据存储,您也需要对它们进行加密。 This 适用于 ASP.NET 核心,但仍然值得一读并在 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 令牌的最佳实践记录?
我尝试了什么?
我已经尝试了以下解决方案,但我仍然不确定它们是否可以被视为良好做法。
- 一个静态 class 具有两个静态属性
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
属性。可能作为另一个 class. 中的静态值
是否有任何其他方法可以在另一个 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 response。 expires_in
等于自 unix 纪元以来的秒数,因此您应该知道令牌何时过期。然后,您可以保存授予数据存储的令牌及其过期时间和刷新令牌。 当您使用缓存时,您可以将缓存条目设置为在其中的令牌过期之前过期分钟。
当您接到下一个 API 电话时,请检查您的数据存储中是否有“有效”令牌。如果否,调用以获取新的 JWT 令牌并使用上述方法持久化它。否则,请尝试使用数据存储中的令牌进行 API 调用。如果您有后台服务,例如 WebJob 或 Hangfire,您可以定期根据令牌验证端点验证所有令牌(如果您的外部 API 提供一个)并在需要时刷新它们。
您应该始终处理未经授权的响应。令牌可以在到期前被撤销。如果您收到未经授权的响应,您可以尝试 re-authenticate 与外部 API 并刷新保存在数据存储中的令牌。如果令牌生成需要让用户参与,您可以 return 401 给您的客户。
最后,您还需要考虑安全性。当您持久化令牌时,即使是您自己的数据存储,您也需要对它们进行加密。 This 适用于 ASP.NET 核心,但仍然值得一读并在 API.
中做类似的事情