12/08/2018, 15:20

Handle refresh token with Retrofit2

Hiện nay chúng ta vẫn thường sử dụng Retrofit cho việc connect với Server, trong phạm vi rất rộng của việc giao tiếp Client-Server thì có quá nhiều thứ chúng ta có thể làm nên mình không đề cập ở đây. Nhưng có 1 khía cạnh nhỏ mà rất hay gặp phải đó là: Refresh Token À đúng rồi đây là điều đã ...

Hiện nay chúng ta vẫn thường sử dụng Retrofit cho việc connect với Server, trong phạm vi rất rộng của việc giao tiếp Client-Server thì có quá nhiều thứ chúng ta có thể làm nên mình không đề cập ở đây. Nhưng có 1 khía cạnh nhỏ mà rất hay gặp phải đó là: Refresh Token À đúng rồi đây là điều đã được biết khi token server gửi về cho client sử dụng làm key chính cho những lần giao tiếp và nó sẽ bị hết hạn trong 2 hoặc vài giờ, 1 ngày , v.v... Làm thế nào để xử lý tốt việc này ? Mình sẽ chia sẻ qua cách đã làm, các bạn có thể tham khảo phía dưới nhé :

1. Create RetrofitClient class

Đầu tiên chúng ta cần có một class trả về Retrofit, ở đây mình tạo với singleton pattern vì không muốn khởi tạo đối tượng Retrofit nhiều lần sẽ không được tối ưu.

RetrofitClient.java

public class RetrofitClient {

    private static Retrofit retrofit = null;

    private RetrofitClient() {
        // constructor nay la private vi the ban khong the khoi tao truc tiep
    }

    public static Retrofit getInstance() {
        if (retrofit == null) {
            /**
             * Minh cung cap 2 cach de xu ly token het han
             * Buoc 2A : dung TokenAuthenticator
             * Buoc 2B : dung TokenInterceptor
             * nen khi cai dat cac ban dung 1 trong 2 thoi nhe onegai ^^
             */

            // Buoc 2A. Detail cac ban xem o phia duoi
            TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();

            // Buoc 2B. Detail cac ban xem o phia duoi
            TokenInterceptor tokenInterceptor = new TokenInterceptor();

            //Chi thuc hien request 1 lan trong cung 1 thoi diem
            //Khi request nay hoan thanh thi request sau moi duoc thuc hien

            Dispatcher dispatcher = new Dispatcher();
            dispatcher.setMaxRequests(1);

            // OkHttp de tao request client-server va add authenticator, interceptors, dispatchers
            OkHttpClient okClient =
                    new OkHttpClient.Builder().connectTimeout(Constants.CONNECT_TIMEOUT,
                            TimeUnit.SECONDS)
                            .readTimeout(Constants.READ_TIMEOUT, TimeUnit.SECONDS)
                            .writeTimeout(Constants.WRITE_TIMEOUT, TimeUnit.SECONDS)
                            .authenticator(tokenAuthenticator)
                            .addInterceptor(tokenInterceptor)
                            .dispatcher(dispatcher)
                            .build();

            retrofit = new Retrofit.Builder().baseUrl(
                    context.getResources().getString(R.string.base_api_url))
                    .addConverterFactory(GsonConverterFactory.create(new Gson()))
                    .client(okClient)
                    .build();
        }

        return retrofit;
    }
}

Đây là một bước quan trọng và là phần chính của bài viết. Khi token trước đó của bạn gửi lên server mà gặp rắc rối với httpCode: 401 Unauthorized HTTP responses thì action refresh token sẽ running. Có 2 cách mình sẽ nêu ra trong bài viết này đó là sử dụng AuthenticatorInterceptor về 2 cách này thì việc sử dụng gần như tương đương và đều cho ra kết quả như mong muốn. Rồi chúng ta cùng bắt đầu cài đặt bước này nhé :

2.A: Handle refresh token

  • Mình tạo ra class TokenAuthenticator TokenAuthenticator.java
public class TokenAuthenticator implements Authenticator {

    @Nullable
    @Override
    public Request authenticate(Route route, Response response) throws IOException {
        String userRefreshToken = "your refresh token";
        String cid = "your client id";
        String csecret = "your client secret";
        String baseUrl = "your base url";

        boolean refreshResult = refreshToken(baseUrl, userRefreshToken, cid, csecret);
        if (refreshResult) {
            //token moi cua ban day
            String accessToken = "your new access token";

            // thuc hien request hien tai khi da lay duoc token moi
            return response.request().newBuilder().header("Authorization", accessToken).build();
        } else {
            //Khi refresh token failed ban co the thuc hien action refresh lan tiep theo
            return null;
        }
    }

    public boolean refreshToken(String url, String refresh, String cid, String csecret)
            throws IOException {
        URL refreshUrl = new URL(url + "token");
        HttpURLConnection urlConnection = (HttpURLConnection) refreshUrl.openConnection();
        urlConnection.setDoInput(true);
        urlConnection.setRequestMethod("POST");
        urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        urlConnection.setUseCaches(false);
        String urlParameters = "grant_type=refresh_token&client_id="
                + cid
                + "&client_secret="
                + csecret
                + "&refresh_token="
                + refresh;

        urlConnection.setDoOutput(true);
        DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
        wr.writeBytes(urlParameters);
        wr.flush();
        wr.close();

        int responseCode = urlConnection.getResponseCode();

        if (responseCode == 200) {
            BufferedReader in =
                    new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();

            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            // this gson part is optional , you can read response directly from Json too
            Gson gson = new Gson();
            RefreshTokenResult refreshTokenResult =
                    gson.fromJson(response.toString(), RefreshTokenResult.class);

            // handle new token ...
            // save it to the sharedpreferences, storage bla bla ...
            return true;
        } else {
            //cannot refresh
            return false;
        }
    }
}

2.B: Handle Interceptor logic

Ở đây mình sử dụng Interceptor để quản lý Request Header, 1 cách thông thường những thông tin quan trọng cần private cao chúng ta thường truyền param vào header , ở đây mình đang chỉ nói tới access token của bạn. Mình sẽ tạo ra class TokenInterceptor

TokenInterceptor.java

public class TokenInterceptor implements Interceptor {
    Context ctx;
    SharedPreferences mPrefs;
    SharedPreferences.Editor mPrefsEdit;

    public TokenInterceptor(Context ctx) {
        this.ctx = ctx;
        this.mPrefs= PreferenceManager.getDefaultSharedPreferences(ctx);
        mPrefsEdit=mPrefs.edit();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {

        Request newRequest=chain.request();

        //when saving expire time :
        integer expiresIn=response.getExpiresIn();
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND,expiresIn);
        mPrefsEdit.putLong("expiretime",c.getTimeInMillis());

        //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
         * see {@link Date#compareTo}
         */
        if(result==-1) {
            //refresh token here , and got new access token
            String newaccessToken="newaccess";
            newRequest=chain.request().newBuilder()
                    .header("Authorization", newaccessToken)
                    .build();
        }
        return chain.proceed(newRequest);
    }
}

3. Conclusion

Sau khi cài đặt theo các bước trên thì bài toán Refresh token with Retrofit2 đã được giải quyết. Với cách làm của mình hy vọng cung cấp thêm cho các bạn sự hữu ích trong việc phát triển ứng dụng. Nếu bạn nào có cách làm hay hơn có thể để lại comment gợi ý cho mình, rất cảm ơn những đóng góp nhiệt tình từ các bạn nha !             </div>
            
            <div class=

0