[英]Android Retrofit2 Refresh Oauth 2 Token
我正在使用Retrofit
和OkHttp
庫。 我有一個Authenticator
驗證器,可以在我們收到401
響應時對用戶進行Authenticator
驗證。
我的build.gradle
是這樣的:
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.okhttp3:okhttp:3.1.2'
我的Authenticator
是這樣的:
public class CustomAuthanticator implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
//refresh access token
refreshTokenResult=apiService.refreshUserToken(parameters);
//this is synchronous retrofit request
RefreshTokenResult refreshResult = refreshTokenResult.execute().body();
//check if response equals 400, means empty response
if(refreshResult != null) {
// save new access and refresh token
// then create a new request and new access token as header
return response.request().newBuilder()
.header("Authorization", newaccesstoken)
.build();
} else {
// we got empty response and we should return null
// if we don't return null
// this method will try to make so many requests to get new access token
return null;
}
}}
這是我的APIService
類:
public interface APIService {
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST("token")
public Call<RefreshTokenResult> refreshUserToken(@Header("Accept") String accept,
@Header("Content-Type") String contentType, @Field("grant_type") String grantType,
@Field("client_id") String clientId, @Field("client_secret") String clientSecret,
@Field("refresh_token") String refreshToken);
}
我正在使用這樣的Retrofit
:
CustomAuthanticator customAuthanticator=new CustomAuthanticator();
OkHttpClient okClient = new OkHttpClient.Builder()
.authenticator(customAuthanticator)
.build();
Retrofit client = new Retrofit.Builder()
.baseUrl(getResources().getString(R.string.base_api_url))
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okClient)
.build();
//then make retrofit request
所以我的問題是:有時我會得到一個新的訪問令牌並繼續工作。 但有時我會收到400
響應,這意味着空響應。 所以我的舊刷新令牌無效,我無法獲得新令牌。 通常我們的刷新令牌會在 1 年內到期。 那么我該如何做到這一點。 請幫我!
免責聲明:實際上我正在使用
Dagger
+RxJava
+Retrofit
但我只是想提供一個答案來為未來的訪問者演示邏輯。
重要提示:如果您從多個地方發出請求,您的令牌將在
TokenAuthenticator
類中多次刷新。 例如,當您的活動和您的服務同時發出請求時。 要解決這個問題,只需將synchronized
關鍵字添加到您的TokenAuthenticator
的authenticate
方法中。
請在
Authenticator
刷新您的令牌時發出同步請求,因為您必須阻塞該線程直到您的請求完成,否則您的請求將使用舊令牌和新令牌執行兩次。 您可以在刷新令牌時使用Schedulers.trampoline()
或blockingGet()
來阻止該線程。
同樣在
authenticate
方法中,您可以通過將請求令牌與存儲的令牌進行比較來檢查令牌是否已經刷新,以防止不必要的刷新。
並且請不要考慮使用
TokenInterceptor
因為它是邊緣情況而不適合所有人,只需關注TokenAuthenticator
。
這就是我們正在努力實現的目標:
首先,刷新令牌是大多數應用程序的關鍵過程。 流程是:如果刷新令牌失敗,則注銷當前用戶並要求重新登錄。 (可能在注銷用戶之前重試刷新令牌幾次)
無論如何,我將逐步解釋它:
第 1 步:請參考單例模式,我們將創建一個負責返回我們的改造實例的類。 由於它是靜態的,如果沒有可用的實例,它只會創建一次實例,並且在您調用它時總是返回這個靜態實例。 這也是單例設計模式的基本定義。
public class RetrofitClient {
private static Retrofit retrofit = null;
private RetrofitClient() {
// private constructor to prevent access
// only way to access: Retrofit client = RetrofitClient.getInstance();
}
public static Retrofit getInstance() {
if (retrofit == null) {
// TokenAuthenticator can be singleton too
TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();
// !! This interceptor is not required for everyone !!
// Main purpose of this interceptor is to reduce server calls
// Our token needs to be refreshed after 10 hours
// We open our app after 50 hours and try to make a request.
// Of course token is expired and we will get a 401 response.
// So this interceptor checks time and refreshes token beforehand.
// If this fails and I get 401 then my TokenAuthenticator does its job.
// if my TokenAuthenticator fails too, basically I just logout the user.
TokenInterceptor tokenInterceptor = new TokenInterceptor();
OkHttpClient okClient = new OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.addInterceptor(tokenInterceptor)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(base_api_url)
.client(okClient)
.build();
}
return retrofit;
}
}
第 2 步:在我的 TokenAuthenticator 的authenticate
方法中:
@Override
public synchronized Request authenticate(Route route, Response response) throws IOException {
boolean refreshResult = refreshToken();
if (refreshResult) {
// refresh token is successful, we saved new token to storage.
// Get your token from storage and set header
String newaccesstoken = "your new access token";
// execute failed request again with new access token
return response.request().newBuilder()
.header("Authorization", newaccesstoken)
.build();
} else {
// Refresh token failed, you can logout user or retry couple of times
// Returning null is critical here, it will stop the current request
// If you do not return null, you will end up in a loop calling refresh
return null;
}
}
和refreshToken
方法,這只是您可以創建自己的示例:
public boolean refreshToken() {
// you can use RxJava with Retrofit and add blockingGet
// it is up to you how to refresh your token
RefreshTokenResult result = retrofit.refreshToken();
int responseCode = result.getResponseCode();
if(responseCode == 200) {
// save new token to sharedpreferences, storage etc.
return true;
} else {
//cannot refresh
return false;
}
}
第3步:對於那些想看TokenInterceptor
邏輯的人:
public class TokenInterceptor implements Interceptor {
SharedPreferences prefs;
SharedPreferences.Editor prefsEdit;
@Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request();
// get expire time from shared preferences
long expireTime = prefs.getLong("expiretime",0);
Calendar c = Calendar.getInstance();
Date nowDate = c.getTime();
c.setTimeInMillis(expireTime);
Date expireDate = c.getTime();
int result = nowDate.compareTo(expireDate);
// when comparing dates -1 means date passed so we need to refresh token
if(result == -1) {
//refresh token here , and get new access token
TokenResponse tokenResponse = refreshToken();
// Save refreshed token's expire time :
integer expiresIn = tokenResponse.getExpiresIn();
Calendar c = Calendar.getInstance();
c.add(Calendar.SECOND,expiresIn);
prefsEdit.putLong("expiretime",c.getTimeInMillis());
String newaccessToken = "new access token";
newRequest=chain.request().newBuilder()
.header("Authorization", newaccessToken)
.build();
}
return chain.proceed(newRequest);
}
}
我在活動和后台服務中提出請求。 他們都使用相同的改造實例,我可以輕松管理訪問令牌。 請參考此答案並嘗試創建您自己的客戶端。 如果您仍然有問題,請在下面發表評論,我會盡力提供幫助。
在您的ApiClient.java類中:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(context))
.build();
在改造包中添加TokenManager.java類
package co.abc.retrofit;
/**
* Created by ravindrashekhawat on 17/03/17.
*/
public interface TokenManager {
String getToken();
boolean hasToken();
void clearToken();
String refreshToken();
}
在包中添加名為AuthorizationInterceptor.java 的Intercepter 類
package co.smsmagic.retrofit;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.gson.Gson;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import co.abc.models.RefreshTokenResponseModel;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
import retrofit2.http.Header;
import static co.abc.utils.abcConstants.ACCESS_TOKEN;
import static co.abc.utils.abcConstants.BASE_URL;
import static co.abc.utils.abcConstants.GCM_TOKEN;
import static co.abc.utils.abcConstants.JWT_TOKEN_PREFIX;
import static co.abc.utils.abcConstants.REFRESH_TOKEN;
/**
* Created by ravindrashekhawat on 21/03/17.
*/
public class AuthorizationInterceptor implements Interceptor {
private static Retrofit retrofit = null;
private static String deviceToken;
private static String accessToken;
private static String refreshToken;
private static TokenManager tokenManager;
private static Context mContext;
public AuthorizationInterceptor(Context context) {
this.mContext = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request modifiedRequest = null;
tokenManager = new TokenManager() {
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
@Override
public String getToken() {
accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
return accessToken;
}
@Override
public boolean hasToken() {
accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
if (accessToken != null && !accessToken.equals("")) {
return true;
}
return false;
}
@Override
public void clearToken() {
sharedPreferences.edit().putString(ACCESS_TOKEN, "").apply();
}
@Override
public String refreshToken() {
final String accessToken = null;
RequestBody reqbody = RequestBody.create(null, new byte[0]);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(BASE_URL + "refresh")
.method("POST", reqbody)
.addHeader("Authorization", JWT_TOKEN_PREFIX + refreshToken)
.build();
try {
Response response = client.newCall(request).execute();
if ((response.code()) == 200) {
// Get response
String jsonData = response.body().string();
Gson gson = new Gson();
RefreshTokenResponseModel refreshTokenResponseModel = gson.fromJson(jsonData, RefreshTokenResponseModel.class);
if (refreshTokenResponseModel.getRespCode().equals("1")) {
sharedPreferences.edit().putString(ACCESS_TOKEN, refreshTokenResponseModel.getResponse()).apply();
return refreshTokenResponseModel.getResponse();
}
}
} catch (IOException e) {
e.printStackTrace();
}
return accessToken;
}
};
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
deviceToken = sharedPreferences.getString(GCM_TOKEN, "");
accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
refreshToken = sharedPreferences.getString(REFRESH_TOKEN, "");
Response response = chain.proceed(request);
boolean unauthorized =false;
if(response.code() == 401 || response.code() == 422){
unauthorized=true;
}
if (unauthorized) {
tokenManager.clearToken();
tokenManager.refreshToken();
accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
if(accessToken!=null){
modifiedRequest = request.newBuilder()
.addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken())
.build();
return chain.proceed(modifiedRequest);
}
}
return response;
}
}
注意:這是我提供的刷新令牌的工作代碼,請保持冷靜,只是為了更改一些常量,除非它可以完美運行。嘗試理解邏輯。
在底部有再次調用相同請求的邏輯
if(accessToken!=null){
modifiedRequest = request.newBuilder()
.addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken())
.build();
return chain.proceed(modifiedRequest);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.