以下是有关 OAuth 2.0 令牌刷新的信息。
定义过期
OAuth 2.0 标准 RFC 6749 将 expires_in
字段定义为到期秒数:
expires_in:推荐。访问令牌的生命周期(以秒为单位)。例如,值“3600”表示访问令牌将在响应生成后一小时内过期。如果省略,授权服务器应该通过其他方式提供过期时间或记录默认值。
令牌刷新处理:方法 1
在收到有效的 access_token
、expires_in
值、refresh_token
等后,客户端可以通过存储过期时间并在每个请求上检查它来处理它。这可以使用以下步骤来完成:
将 expires_in 转换为过期时间(epoch、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
以延长会话的生命周期。
令牌刷新处理:方法 2
另一种处理令牌刷新的方法是在收到无效令牌授权错误后手动刷新。这可以通过以前的方法或单独完成。
如果您尝试使用过期的 access_token
并收到无效令牌错误,则应执行令牌刷新(如果您的刷新令牌仍然有效)。由于不同的服务可以对过期令牌使用不同的错误代码,因此您可以跟踪每个服务的代码,或者跨服务刷新令牌的简单方法是在遇到 4xx 错误时简单地尝试一次刷新。
无效的访问令牌错误
以下是一些流行服务的错误代码:
Facebook:错误 467 访问令牌无效 - 访问令牌已过期、被撤销或无效 - 处理过期的访问令牌。 LinkedIn:错误 401 未经授权。贝宝:错误 401 未经授权。
实现
Zapier服务是一种实现授权错误重试后刷新的服务。
https://i.stack.imgur.com/P8K0z.png
刷新令牌过期
如果您的 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 将返回具有静态时间的刷新令牌,因此如果令牌刷新和刷新令牌更新一致完成,用户不必再次进行身份验证。
推荐上面的方法 2,因为 401 可能由于多种原因而发生,例如更新令牌签名证书或时钟差异:
每次 API 请求后检查 401
获取新令牌 - 仅一次
重试 API 请求 - 仅一次
我已经实现了很多成功的 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;
}
refresh_token
是否无效? :Orefresh_token_expires_in
属性,我已使用此信息更新了答案。