如何识别 OAuth 令牌是否已过期?

How to identify if the OAuth token has expired?

我的 iOS 移动应用程序使用使用 OAuth2.0 协议实现的服务。 OAuth 访问令牌带有一个刷新令牌和一个 expires_in 字段。我在我的应用程序中保存了刷新令牌和访问令牌过期时间,但不知道何时使用它们。

以下是有关 OAuth 2.0 令牌刷新的信息。

在定义中过期

OAuth 2.0 标准 RFC 6749expires_in 字段定义为到期秒数:

expires_in: RECOMMENDED. The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value.

令牌刷新处理:方法 1

收到有效的 access_tokenexpires_in 值、refresh_token 等后,客户端可以通过存储过期时间并在每次请求时检查它来进行处理。这可以使用以下步骤完成:

  1. expires_in 转换为过期时间(纪元、RFC-3339/ISO-8601 日期时间等)
  2. 存储过期时间
  3. 在每个资源请求上,检查当前时间与过期时间,如果 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 错误时尝试一次刷新。

无效访问令牌错误

以下是一些流行服务的错误代码:

  1. Facebook: Error 467 Invalid access token - 访问令牌已过期、被撤销或无效 - 处理过期的访问令牌。
  2. LinkedIn: Error 401 Unauthorized.
  3. PayPal: Error 401 Unauthorized.

实施

Zapier服务是一种实现授权错误重试后刷新的服务。

刷新令牌到期

如果您的refresh_token也已过期,则需要重新进行授权。

OAuth 2.0 spec 没有定义刷新令牌过期或如何处理它,但是,一些 API 将 return 一个 refresh_token_expires_in 属性 当刷新令牌过期时。不同的 API 会以不同的方式处理刷新令牌过期,因此查看每个 API 的文档很重要,但通常您在刷新访问令牌时可能会收到一个新的刷新令牌。过期应该以类似的方式处理,例如将 refresh_token_expires_in 转换为 RFC 3339 日期时间 refresh_token_expiry 值。

一些示例包括 LinkedIn, eBay, and RingCentral。在 LinkedIn API 中,当您刷新访问令牌时,您将收到一个递减 refresh_token_expires_in 属性 的刷新令牌,目标是原始刷新令牌的到期时间,直到您需要再次进行身份验证。 RingCentral API 将 return 使用静态时间刷新令牌,因此如果令牌刷新和刷新令牌更新一致完成,用户无需再次授权。

会推荐上面的方法 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;
    }