如何判断OAuth令牌是否已过期?

85

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

  • 那么,通常和最佳实践是如何使用这个expires_in的呢?
  • 如何确定我的访问令牌已过期?
  • 是否有一种常见的Web服务错误格式,可以说明我的访问令牌已过期?
3个回答

145

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

过期时间定义

OAuth 2.0标准RFC 6749expires_in字段定义为距离过期还有多少秒:

expires_in:建议使用。访问令牌的生命周期(以秒为单位)。例如,值“3600”表示访问令牌将在响应生成时的一小时后过期。如果未提供,则授权服务器应通过其他方式提供到期时间或记录默认值。

令牌刷新处理:方法1

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

  1. expires_in转换为到期时间 (epoch, RFC-3339/ISO-8601 datetime等)
  2. 存储到期时间
  3. 在每次资源请求时,检查当前时间是否超过到期时间,如果access_token已过期,则在资源请求之前进行令牌刷新请求

一个示例实现是Go oauth2库,它将expires_in值转换为RFC 3339日期时间,并在Token的expiry属性中。OAuth 2.0标准没有定义expiry,但在这里很有用。

在检查时间时,请确保使用相同的时间,例如通过将所有时间转换为epoch或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服务是一项实现授权错误重试后刷新的服务。

enter image description here

刷新令牌过期

如果您的refresh_token也已过期,则需要再次进行授权过程。

OAuth 2.0规范未定义刷新令牌的过期时间和处理方式,但许多API在刷新令牌过期时会返回一个refresh_token_expires_in属性。不同的API会以不同的方式处理刷新令牌的过期,因此重要的是根据每个API的文档进行审查,但通常情况下,在刷新访问令牌时可能会接收到新的刷新令牌。应该以类似的方式处理过期,例如将refresh_token_expires_in转换为RFC 3339日期时间的refresh_token_expiry值。

一些示例包括LinkedIneBayRingCentral。在LinkedIn API中,当您刷新访问令牌时,您将收到一个带有递减的refresh_token_expires_in属性的刷新令牌,指向原始刷新令牌到期时间,直到需要再次进行身份验证。RingCentral API将返回具有静态时间的刷新令牌,因此如果一致地进行令牌刷新和更新,则用户无需再次进行身份验证。


你好,非常感谢您的回复!在检查过期时间时,如果用户更改设备时间会怎样?这不会导致访问令牌刷新得太频繁吗?关于检查无效令牌错误,您是否了解oAuth服务器将返回响应代码和错误格式的任何想法?这个错误响应对所有oAuth服务器都是相同的吗? - XiOS
经常发生设备时间更改的一些用例是什么?时区更改应该自动处理。由于标准中未定义无效令牌错误代码,因此不同的服务现在选择了不同的错误代码,如上所列。一个简单的刷新方法可能是在出现4xx错误时尝试单个刷新。 - Grokify
晚来一步,但你怎么知道refresh_token无效? :O - Nobita
1
IETF RFC 6749并没有定义刷新令牌的过期时间或如何处理它,但许多API实现了refresh_token_expires_in属性,我已经更新了答案并提供了这些信息。 - Grokify
我的理解是,你不能通过浏览器上的JavaScript读取cookie。因此,如果将JWT存储在https浏览器cookie中,您无法检查到期日期,对吗?所以,最好的选择就是选项2,每当收到未经授权的响应时重试。 - wongz
显示剩余3条评论

11

推荐使用方法2,因为401错误可能由多种原因引起,例如更新令牌签名证书或时钟差异:

  • 在每个API请求后检查401
  • 获取新令牌-仅一次
  • 重试API请求-仅一次

我已经实现了很多成功的OAuth客户端,并始终使用此技术-并避免在客户端代码中阅读expires_in字段。


3
我原本打算在我的应用程序中实现代码来检查expires_in字段,但由于时区和用户更改设备时间等问题而遇到了很多问题。因此,我将按照您上面所说的方法来实现我的NetworkingClient。 - CoderMan
5
作为令牌发行方返回的过期时间,不应受用户时区影响,我希望它是基于UTC(世界协调时间)的。 - Joe Eng

1
这个问题虽然指定了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;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接