【发布时间】:2015-08-29 20:38:30
【问题描述】:
我的 iOS 移动应用使用通过 OAuth2.0 协议实现的服务。 OAuth 访问令牌附带一个刷新令牌和一个expires_in 字段。我在我的应用中保存了刷新令牌和访问令牌过期时间,但不知道何时使用它们。
- 那么使用此
expires_in的通常和最佳做法是什么? - 如何识别我的访问令牌已过期?
- 是否有一种常见的 Web 服务错误格式显示我的访问令牌已过期?
【问题讨论】:
我的 iOS 移动应用使用通过 OAuth2.0 协议实现的服务。 OAuth 访问令牌附带一个刷新令牌和一个expires_in 字段。我在我的应用中保存了刷新令牌和访问令牌过期时间,但不知道何时使用它们。
expires_in 的通常和最佳做法是什么?【问题讨论】:
这是有关 OAuth 2.0 令牌刷新的信息。
OAuth 2.0 标准 RFC 6749 将 expires_in 字段定义为到期秒数:
expires_in:推荐。访问令牌的生命周期(以秒为单位)。例如,值“3600”表示访问令牌将在响应生成后一小时内过期。如果省略,授权服务器应该通过其他方式提供过期时间或记录默认值。
在收到有效的access_token、expires_in 值、refresh_token 等后,客户端可以通过存储过期时间并在每个请求上检查它来处理此问题。这可以通过以下步骤完成:
expires_in 转换为过期时间(纪元、RFC-3339/ISO-8601 日期时间等)access_token 已过期,则在资源请求之前发出令牌刷新请求一个示例实现是 Go oauth2 库,它将 expires_in 值转换为令牌 expiry property 中的 RFC 3339 日期时间。 expiry 不是由 OAuth 2.0 标准定义的,但在这里很有用。
检查时间时,请确保您是同一时间,例如,通过将所有时间转换为纪元或 UTC 时区来使用相同的时区。
除了收到一个新的access_token 之外,您将来可能还会收到一个新的refresh_token,其到期时间会更长。如果您收到此邮件,您应该存储新的 refresh_token 以延长会话的生命周期。
处理令牌刷新的另一种方法是在收到无效令牌授权错误后手动刷新。这可以通过以前的方法完成,也可以单独完成。
如果您尝试使用过期的access_token 并收到无效令牌错误,则应执行令牌刷新(如果您的刷新令牌仍然有效)。由于不同的服务可以对过期令牌使用不同的错误代码,因此您可以跟踪每个服务的代码,或者跨服务刷新令牌的简单方法是在遇到 4xx 错误时尝试一次刷新。
以下是一些流行服务的错误代码:
Zapier服务是一种实现授权错误重试后刷新的服务。
如果您的refresh_token 也已过期,则需要重新进行授权。
OAuth 2.0 spec 没有定义刷新令牌过期或如何处理它,但是,当刷新令牌过期时,许多 API 将返回 refresh_token_expires_in 属性。不同的 API 会以不同的方式处理刷新令牌过期,因此查看每个 API 的文档很重要,但通常您在刷新访问令牌时可能会收到一个新的刷新令牌。过期应该以类似的方式处理,例如将 refresh_token_expires_in 转换为 RFC 3339 日期时间 refresh_token_expiry 值。
一些示例包括LinkedIn、eBay 和RingCentral。在 LinkedIn API 中,当您刷新访问令牌时,您将收到一个带有递减 refresh_token_expires_in 属性的刷新令牌,该属性以原始刷新令牌到期时间为目标,直到您需要再次进行身份验证。 RingCentral API 将返回具有静态时间的刷新令牌,因此如果令牌刷新和刷新令牌更新一致完成,用户不必再次进行身份验证。
【讨论】:
refresh_token 是否无效? :O
refresh_token_expires_in 属性,我已经用这个信息更新了答案。
推荐上面的方法 2,因为 401 可能由于多种原因发生,例如更新令牌签名证书或时钟差异:
我已经实现了许多成功的 OAuth 客户端并且一直使用这种技术 - 并且避免在我的客户端代码中读取 expires_in 字段
【讨论】:
该问题指定了 iOS,但作为任何工具集的一般原则,基于服务器的解决方案将令牌存储在服务器内存缓存中,并将缓存到期日期时间设置为与令牌的到期时间相同。
在需要身份验证令牌的任何其他端点之前调用以下函数。
这样它会从缓存中获取令牌,或者如果缓存过期(与令牌本身过期的时间相同)则获取新令牌。
对于 .NET:
private async Task<string> GetAuthToken()
{
string cacheKey = "AuthToken";
if (!_memoryCache.TryGetValue(cacheKey, out string authToken))
{
// Token not in cache, so get fresh one:
// Do call for token
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, *url*);
// Add Headers
...
// Make call
var response = await client.SendAsync(request);
string responseContent = await response.Content.ReadAsStringAsync();
// Check the call success/failure
if (!response.IsSuccessStatusCode)
{
return null;
}
JObject authObj = JObject.Parse(responseContent);
authToken = (string)authObj["access_token"];
string authTokenExpires = (string)authObj["expires_in"];
// Save data in cache.
MemoryCacheEntryOptions staticDataCacheMemoryOptions = new MemoryCacheEntryOptions()
// Keep in cache until expired by Provider
.SetAbsoluteExpiration(DateTime.Now.AddSeconds(Convert.ToInt32(authTokenExpires)));
_memoryCache.Set(cacheKey, authToken, staticDataCacheMemoryOptions);
}
return authToken;
}
【讨论】: