刷新客户端凭证流的访问令牌

Refreshing an Access Token for Client Credentials Flow

我想知道刷新通过 OAuth 2.0 中的客户端凭据流获取的访问令牌的最佳方法是什么。我已经阅读了规范,但我似乎无法找到适合我的特定情况的答案。

对于我的具体情况,我正在为我的 Android 应用程序使用 Spotify Web API 来搜索内容(曲目、专辑、and/or 艺术家)。为了执行搜索,我需要一个访问令牌。由于我对 Spotify 用户的数据不感兴趣,我可以使用客户端凭据流来获取访问令牌,这在 Spotify 的条款中有解释 here.

因为访问令牌最终会过期,所以我必须想办法在过期后刷新它。我最终想知道的是我的方法是否有效,以及我的方法是否有任何问题。

首先,我将访问令牌存储在 SharedPreferences 中。在 onCreate() 内,我有以下内容:

@Override
protected void onCreate(Bundle savedInstanceState) {
    // A bunch of other stuff, views being initialized, etc.


    mAccessToken = getAccessToken();

    // If the access token is expired, then we will attempt to retrieve a new one
    if (accessTokenExpired()) {
        retrieveAccessToken();
    }
}

我定义了 accessTokenExpired()retrieveAccessToken() 如下:

private boolean accessTokenExpired() {
    // If mAccessToken hasn't yet been initialized, that means that we need to try to retrieve
    // an access token. In this case, we will return true;
    if (mAccessToken == null) {
        return true;
    }

    SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
    long timeSaved = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_TIME_SAVED, 0L);
    long expiration = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_EXPIRATION, 0L);
    long now = System.currentTimeMillis()/1000;
    long timePassed = Math.abs(now - timeSaved);

    if (timePassed >= expiration) {
        return true;
    } else {
        return false;
    }
}

关于 retrieveAccessToken() 值得注意的一件事是我使用 Retrofit 作为我的 HTTP 请求:

private void retrieveAccessToken() {

    // First, we obtain an instance of SearchClient through our ClientGenerator class
    mClient = ClientGenerator.createClient(SearchClient.class);

    // We then obtain the client ID and client secret encoded in Base64.
    String encodedString = encodeClientIDAndSecret();

    // Finally, we initiate the HTTP request and hope to get the access token as a response
    Call<TokenResponse> tokenResponseCall = mClient.getAccessToken(encodedString, "client_credentials");
    tokenResponseCall.enqueue(new Callback<TokenResponse>() {
        @Override
        public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {
            Log.d(TAG, "on Response: response toString(): " + response.toString());
            TokenResponse tokenResponse = null;
            if (response.isSuccessful()) {
                tokenResponse = response.body();
                Log.d(TAG, tokenResponse.toString());
                mAccessToken = tokenResponse.getAccessToken();
                saveAccessToken(tokenResponse);
            }
        }

        @Override
        public void onFailure(Call<TokenResponse> call, Throwable t) {
            Log.d(TAG, "onFailure: request toString():" + call.request().toString());
            mAccessToken = "";
        }
    });
}

最后,saveAccessToken(tokenResponse)accessTokenExpired() 的补充,我将令牌响应中的值保存到 SharedPreferences 而不是检索它们。

我这样做有什么问题吗?我从 那里得到了灵感,并稍作修改。 Spotify 不在其访问令牌响应中提供刷新令牌。因此,我不能在这里使用它来减少我发出的访问令牌请求的数量。

如有任何意见,我们将不胜感激!

两个注意事项是:

  • 您可能希望针对使用可以处理令牌过期并重试的访问令牌发出的请求进行一些错误处理。这会有所帮助的两种情况是
    1. 当令牌在检查它是否有效和您使用它之间过期时
    2. check the token is valid -> make some requests with the token -> repeat 的周期中,您使用令牌花费了一个多小时。另一种方法是计算 now + expected_api_request_time > token_expiration_time ,其中 expected_api_request_time 将是您设置的常量,但我认为将令牌过期作为异常处理是更好的做法(您可能希望能够重试在网络不稳定的情况下)。
  • 您可以在从本地存储中检索 timeSavedexpiration 时计算令牌何时过期,或者只计算令牌最初过期的时间并保存那。这是相对较小的,我认为这和您完成它的方式都很好。