【问题标题】:How to avoid refreshing token multiple times?如何避免多次刷新令牌?
【发布时间】:2016-09-20 10:59:11
【问题描述】:

在我的一个应用程序中,我已与 Infusionsoft 集成,并且访问令牌会在特定时间后过期。

现在前端发出多个请求以获取不同的数据。当令牌过期时,它会刷新令牌并获取新的访问和刷新令牌。但是在我获得新的访问和刷新令牌之前,来自 UI 的后续请求会尝试使用旧的刷新令牌刷新令牌,它们都会导致错误。

解决此问题的最佳方法是什么?

【问题讨论】:

    标签: api oauth infusionsoft


    【解决方案1】:

    (我的回答并非针对 Infusionsoft)。

    当 Web 服务客户端可能发出使用相同承载令牌的并发请求(以防止每个请求线程或异步上下文产生它自己单独的刷新请求)。

    诀窍是使用缓存的Task<AuthResponseDto>(其中AuthResponseDto是包含最新成功获得的access_token的DTO类型)在lock中交换(你不能awaitlock 内,但您可以在 lock 内复制 Task 引用,然后在 lock 外复制 await

    // NOTE: `ConfigureAwait(false)` calls omitted for brevity. You should re-add them back.
    
    class MyHttpClientWrapper
    {
        private readonly String refreshTokenOrClientCredentialsOrWhatever;
    
        private readonly IHttpClientFactory hcf;
    
        private readonly Object lastAuthTaskLock = new Object();
    
        private Task<AuthResponseDto> lastAuthTask;
        private DateTime lastAuthTaskAt;
    
        public MyHttpClientWrapper( IHttpClientFactory hcf )
        {
            this.hcf = hcf ?? throw new ArgumentNullException( nameof(hcf) );
    
            this.refreshTokenOrClientCredentialsOrWhatever = LoadFromSavedConfig();
        }
    
        private async Task<AuthResponseDto> RefreshBearerTokenAsync()
        {
            using( HttpClient hc = this.hcf.CreateClient() )
            using( HttpResponseMessage resp = await hc.PostAsync( this.refreshTokenOrClientCredentialsOrWhatever ) )
            {
                AuthResponseDto ar = await DeserializeJsonResponseAsync( resp );
                this.lastAuthTaskExpiresAt = DateTime.UtcNow.Add( ar.MaxAge );
                return ar;
            }
        }
    
        private async Task<String> RefreshBearerTokenIfNecessaryAsync()
        {
            Task<AuthResponseDto> task;
    
            lock( this.lastAuthTaskLock )
            {
                if( this.lastAuthTask is null )
                {
                    // e.g. This is the first ever request.
    
                    task = this.lastAuthTask = this.RefreshBearerTokenAsync();
                }
                else
                {
                    task = this.lastAuthTask;
                    
                    // Is the task currently active? If it's currently busy then just await it (thus preventing duplicate requests!)
                    if( task.IsCompleted )
                    {
                        // If the current bearer-token is definitely expired, then replace it:
                        if( this.lastAuthTaskExpiresAt <= DateTime.UtcNow )
                        {
                            task = this.lastAuthTask = this.RefreshBearerTokenAsync();
                        }
                    }
                    else
                    {
                        // Continue below.
                    }
                }
            }
    
            AuthResponseDto ar = await task;
            return ar.BearerToken;
        }
    
        //
    
        public async Task<CustomerDto> GetCustomerAsync( Int32 customerId )
        {
            // Always do this in every request to ensure you have the latest bearerToken:
            String bearerToken = await this.RefreshBearerTokenIfNecessaryAsync();
            
            using( HttpClient hc = this.hcf.Create() )
            using( HttpRequestMessage req = new HttpRequestMessage() )
            {
                req.Headers.Add( "Authorization", "Bearer " + bearerToken );
                using( HttpResponseMessage resp = await hc.SendAsync( req ) )
                {
                    if( resp.StatusCode == 401 )
                    {
                        // Authentication error before the token expired - invoke and await `RefreshBearerTokenAsync` (rather than `RefreshBearerTokenIfNecessaryAsync`) and see what happens. If it succeeds then re-run `req`) otherwise throw/fail because that's an unrecoverable error.
                    }
    
                    // etc
                }
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      【讨论】:

      • 嗨迈克尔,让我问你 24 小时时间表有多可靠,因为前段时间大约 8 小时?
      • 按计划刷新令牌对我来说似乎是一个弱解决方案,当您的服务器需要处理数千或数百万个帐户时会发生什么 - 谈论需要用户凭据的三足令牌?即使他们偶尔登录您的应用程序,您也会不断为每个用户刷新这些令牌......听起来有点矫枉过正!
      • 我对此投了反对票,因为这是个坏建议。在分布式计算中——尤其是分布式身份验证(例如 OIDC、OAuth2 等)中,你不能信任——也不应该信任任何东西——包括 refresh_token 或承载令牌到期时(因为它可能是单方面的)期满前撤销)。在处理“引用令牌”样式的 access_token 值时,这也是一个真正的问题,这些值只是像 GUID 这样不公开任何到期日期的短不透明字符串 - 你不会知道 access_token 不起作用直到您从服务器收到错误消息。
      猜你喜欢
      • 2022-10-31
      • 1970-01-01
      • 1970-01-01
      • 2020-06-30
      • 2020-10-13
      • 1970-01-01
      • 1970-01-01
      • 2020-07-12
      • 2019-05-07
      相关资源
      最近更新 更多