Android Retrofit2 Refresh Oauth 2 Token

前端 未结 2 800
旧时难觅i
旧时难觅i 2021-01-30 07:17

I am using Retrofit and OkHttp libraries. So I have Authenticator which authanticate user if gets 401 response.

My build.gra

相关标签:
2条回答
  • 2021-01-30 07:43

    In your ApiClient.java class :

    OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(new AuthorizationInterceptor(context))
                    .build();
    

    Add TokenManager.java class in your retrofit package

    package co.abc.retrofit;
    
    /**
     * Created by ravindrashekhawat on 17/03/17.
     */
    
    public interface TokenManager {
        String getToken();
        boolean hasToken();
        void clearToken();
        String refreshToken();
    }
    

    Add Intercepter class in your package with name AuthorizationInterceptor.java

    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;
        }
    }
    

    Note : This is working code for refresh token that I have provided stay calm you just to change some constant except that it will work perfectly.Just try to understand the logic .

    In bottom there is logic to call again the same request

     if(accessToken!=null){
                    modifiedRequest = request.newBuilder()
                            .addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken())
                            .build();
                    return chain.proceed(modifiedRequest);
      }
    
    0 讨论(0)
  • 2021-01-30 07:46

    Disclaimer : Actually I am using Dagger +RxJava + RxAndroid + Retrofit but I just wanted to provide an answer to demonstrate logic for future visitors. Only difference is use Schedulers.trampoline() or blockingGet() when refreshing your token to block that thread. If you have more questions about these libraries please comment below so maybe I can provide another answer or help you.

    Important : If you are making requests from several places your token will refresh multiple times inside TokenAuthenticator class. For example when your app and your service makes requests concurrently. To beat this issue just add synchronized keyword to your TokenAuthenticators authenticate method.

    Also inside authenticate method you can check if token already refreshed by comparing request token with stored token to prevent unnecessary refresh

    And please do not consider using TokenInterceptor because it is edge case, just focus TokenAuthenticator.

    First of all refreshing token is critical process for most apps. Many apps works like that: If refresh token fails logout current user and warn user to re-login.(Maybe retry refresh token couple token before logout user)

    Important Notice: Please make synchronous requests when refreshing your token inside Authenticator because you must block that thread until your request finish otherwise your requests executed twice with old and new token.

    Anyways I will explain it step by step :

    Step 1 : Please refer singleton pattern, we will create one class thats responsible for returning our retrofit instance. Since it is static if there is no instance available it just creates instance only once and when you call it always returns this static instance. This is also basic definition of Singleton design pattern.

    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) {
            // our authenticator
            TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();
    
            // !! This interceptor is not required for everyone !!
            // Main purpose is this interceptor is reducing server calls
    
            // Our token needs to be refreshed after 10 hours
            // We came application after 50 hours and try to make request.
            // Of course token is expired and it will return 401
            // So this interceptor checks time and refreshes token before any 401
            // If this fails and I get 401 then my TokenAuthenticator do his job.
            // if my TokenAuthenticator fails too, basically I just logout 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;
    }
    
    }
    

    Step 2 : In my TokenAuthenticator's authenticate method :

    @Override
    public Request authenticate(Route route, Response response) throws IOException {
    
        refreshResult=refreshToken();
        if (refreshResult) {
        //refresh is successful, we saved new token to storage
        // get your token from storage and set header
        String newaccess="your new access token";
    
        // make current request with new access token
        return response.request().newBuilder()
                .header("Authorization", newaccess)
                .build();
    
        } else {
            // Refresh token failed, you can logout user or retry couple times
            // Returning null is critical here, it will stop current request
            // If you do not return null, the request gets called again and again
            // You will end up in a loop calling refresh again and again
            return null;
        }
    }
    

    And refreshToken method, this is just example you can create your own:

    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;
        } 
    }
    

    Step 3: For those who wants to see TokenInterceptor logic:

    public class TokenInterceptor implements Interceptor{
    SharedPreferences mPrefs;
    SharedPreferences.Editor mPrefsEdit;
    
    @Override
    public Response intercept(Chain chain) throws IOException {
    
        Request newRequest=chain.request();
    
        //get expire time from shared preferences
        long expireTime=mPrefs.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);
            mPrefsEdit.putLong("expiretime",c.getTimeInMillis());
    
            String newaccessToken="new access token";
            newRequest=chain.request().newBuilder()
                    .header("Authorization", newaccessToken)
                    .build();
        }
        return chain.proceed(newRequest);
      }
    }
    

    I am making requests at application and background service. Both of them uses same retrofit instance and I can easily manage requests. Please refer this answer and try to create your own client. If you still have issues simply comment below or send mail. I'll try to help. Hope this helps.

    0 讨论(0)
提交回复
热议问题