【问题标题】:Should you serialize calls to refresh an oauth token您是否应该序列化调用以刷新 oauth 令牌
【发布时间】:2021-07-18 03:15:36
【问题描述】:

对于我的场景,我使用 ASP.NET Core 5 和 AzureAD(或 IdentityServer4 进行测试),但这可能会改变,因此请寻找任何一般和具体的指导。

注意:我在 Will an old refresh token still be valid if a new refresh token get issued? 看到了答案,但不清楚这是标准行为,还是 Google 特定的行为(问题所在)。

当可能发生并发请求时,处理刷新令牌的正确模式是什么?

在我的代码中,我正在捕获经过身份验证的刷新令牌和到期时间,即

                options.Events = new OpenIdConnectEvents
                {
                    // This is called during sign-in after the access token and refresh token are received
                    OnTokenResponseReceived = context =>
                    {
                        // Extract just the refresh token and put it in the cookie. (Note: This is ~64 bytes on IdentityServer4, but over 1KB in AzureAD)
                        string token = context.TokenEndpointResponse.RefreshToken;
                        string expires = context.TokenEndpointResponse.ExpiresIn; // e.g. "3600"
//...save the values in the ticket/cookie...

稍后在 OnValidatePrincipal 中,我正在检查令牌是否已过期,如果是,则尝试更新它,例如

            var pairs = new Dictionary<string, string>() {
                { "client_id", oidcOptions.ClientId },
                { "client_secret", oidcOptions.ClientSecret },
                { "grant_type", "refresh_token" },
                { "refresh_token", refresh_token }
            };

            var result = await httpClient.PostAsync(oidcOptions.Authority + "/oauth2/token", new FormUrlEncodedContent(pairs));
// ... If successful, update the ticket with the new values..

现在假设令牌已过期,而浏览器恰好同时发送了 5 个新的 HTTP 请求(例如,在页面上加载了一堆图像)。如果服务器尝试同时处理所有这些请求,它将尝试同时兑换相同的 refresh_token。

https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview#token-types 的页面指出:

保存新的刷新令牌。它替换了您之前在请求中使用的刷新令牌。

那么旧的刷新令牌一旦使用就不再有效了吗?这意味着第一个请求成功并且旧的刷新令牌无效,并且所有其他正在进行的请求都将失败(因为刷新令牌将无法赎回已被使用的)。

如果是这种情况,我想使用刷新令牌的正确逻辑可能会非常复杂。 (如果没有,该机构是否应该维护一个有效刷新令牌的列表,或者允许“当前”和“新”令牌在一段时间内的有效性重叠?)

=== 更新 ===

作为额外信息,我只是对这两个提供程序进行了测试,但只是添加了额外的几行代码以进行两次相同的调用以刷新令牌(通过单步执行调试器会稍有延迟)。

            var result = await httpClient.PostAsync(oidcOptions.Authority + oidcOptions.TokenEndpoint, new FormUrlEncodedContent(pairs));

            if (runtwice)
            {
                var result2 = await httpClient.PostAsync(oidcOptions.Authority + oidcOptions.TokenEndpoint, new FormUrlEncodedContent(pairs));
                var response1_body = await result.Content.ReadAsStringAsync();
                var response2_body = await result2.Content.ReadAsStringAsync();
                Trace.WriteLine(response1_body);
                Trace.WriteLine(response2_body);
            }

使用 AzureAD,它确实可以正常工作,并给了我两个单独的令牌,用于具有相同刷新令牌的调用:

// First call
{
    "token_type": "Bearer",
    "expires_in": "3599",
    "ext_expires_in": "3599",
    "expires_on": "1619227041",
    "access_token": "PAQABA...XeZLgIAA",
    "refresh_token": "0.ATU...Bw_yB09Be8"
}

// Second call
{
    "token_type": "Bearer",
    "expires_in": "3599",
    "ext_expires_in": "3599",
    "expires_on": "1619227046",
    "access_token": "PAQABA...fLyZQIAA",
    "refresh_token": "0.ATU...K8y7zOm1CQ"
}

使用 IdentityServer4,它拒绝了使用相同刷新令牌发出的第二个请求

// First call
{
  "id_token":"eyJhbG...",
  "access_token":"eyJhbGciOi...",
  "expires_in":3600,
  "token_type":"Bearer",
  "refresh_token":"6D141...",
  "scope":"openid profile email offline_access"
}

// Second call
{"error":"invalid_grant"}

我仍然不确定哪个是预期的行为,或者它是否是实现定义的(或可配置的)。

(注意:对于 IdentityServer4 似乎是可配置的:http://docs.identityserver.io/en/latest/topics/refresh_tokens.html#customizing-refresh-token-behavior

【问题讨论】:

    标签: oauth azure-active-directory identityserver4 refresh-token


    【解决方案1】:

    这是一种最佳配置做法,以便您只接受刷新令牌的一次性使用。因此,如果您使用它两次,您就会退出。每次使用刷新令牌都会获得一个新令牌。

    为什么?这是一个安全预防措施,所以如果刷新令牌被盗并且黑客使用了刷新令牌,那么下次您使用刷新令牌时 IdentityServer 会注意到。

    您需要小心不要在您的客户端中出现任何竞争条件,以免您意外使用刷新令牌两次。如果您使用 IdentityModel 库,它可以为您处理。

    【讨论】:

    • 我认为如果向外扩展仍然是个问题——你可以让 2 个服务器独立地尝试使用相同的刷新令牌——实际上你需要一个分布式锁来保证安全更新。幸运的是,一次性刷新令牌在服务器端场景(令牌永远不会离开后端)中不如客户端场景(例如在本机移动应用程序中)重要。幸运的是,客户端流程使执行序列化更新变得更加容易。
    • 没错。如果没有某种分布式锁定机制,我看不出你如何保证在横向扩展的场景中不会使用相同的刷新令牌发出并发请求。
    • IdentityServer4 文档说明如下。短宽限期听起来是个不错的解决方案:如果收到已使用的令牌,默认服务将调用名为 AcceptConsumedTokenAsync 的虚拟方法。默认实现将拒绝请求,但您可以在此处实现自定义逻辑,如宽限期,或撤销额外的刷新或访问令牌。
    猜你喜欢
    • 2015-07-08
    • 2015-07-16
    • 2021-05-14
    • 2016-10-07
    • 2018-09-09
    • 2015-02-28
    • 2020-05-05
    • 2014-07-09
    • 2017-11-20
    相关资源
    最近更新 更多