Chuyển đến nội dung chính

Handle refresh token with Retrofit2

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 Authenticator và Interceptor 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 ! 

Nhận xét

Bài đăng phổ biến từ blog này

Camera2 - Android

Ở bài viết này mình xin giới thiệu về cách sử dụng Camera2 trong android SDK 21. Với các lập trình viên android việc sử dụng Camera có rất nhiều trong ứng dụng: Camera Capture Images, Barcode - QR Code Reader, AR, Video Record,.... Nhiều ứng dụng chỉ ở tầng ứng dụng sử dụng thông qua  Intent  như vậy hệ thống tự động được gọi ở mức tối ưu nhất, nhưng cũng không ít ứng dụng cần can thiệp vào tầng  native  để xử lý Với  Camera   developer.android.com  đã  deprecate  nó đã không còn được sử dụng cơ bản trong các ứng dụng nữa vì rất nhiều nguyên nhân trong đó phải kể tới: tốn tài nguyên, thời gian capture khá chậm và đặc biệt phục vụ nhu cầu ngày càng cao của người dùng như 'Chụp ảnh liên tục, chụp nhiều ảnh và tự động lấy nét' thì  Camera không đáp ứng được Với  Camera2  đã đáp ứng được những thiếu xót trên ngoài ra việc customize cho ảnh là rất dễ dàng mang lại chất lượng cao ngoài ra việc sử dụng cũng không có nhiều thay ...

Sự khác nhau giữa let, apply, with, run và also trong Kotlin

Với những ai đã sử dụng Kotlin để phát triển ứng dụng, chắc hẳn đã không ít lần sử dụng các standard functions run, with, let, also và apply. Để hiểu và sử dụng thành thục chúng không phải là dễ. Và dưới đây là những điều đúc kết lại được. https://viblo.asia/p/su-khac-nhau-giua-let-apply-with-run-va-also-trong-kotlin-RQqKLNdml7z Scoping functions Có thể hiểu đơn giản, scoping function là phạm vi ảnh hưởng nhất định của một hàm. Nó là điều cốt lõi để phân biệt giữa các scoping functions: run, with, T.run, T.let, T.also và T.apply. Dưới đây là minh hoạ phạm vi của hàm run: fun test ( ) { var mood = "I am sad" run { val mood = "I am happy" println ( mood ) // I am happy } println ( mood ) // I am sad } Ở trên, ta có thể thấy rõ ràng trong phạm vi của hàm run, biến mood đã được định nghĩa lại trước khi in ra mà không làm ảnh hưởng tới phần khác của chương trình 3 attributes of scoping functions 1.Normal vs. extension fun...

TỰ ĐỘNG HUỶ ACTIVITY SAU KHI STARTACTIVITY

Trước giờ để huỷ một Activity khi bạn thường dùng hàm  finish()  đúng không nào? Không đi đâu xa là khi bạn Intent từ một Activity này sang Activity khác mà muốn huỷ luôn Activity đầu tiên luôn thì bạn sẽ dùng đoạn code y chang bên dưới chứ? Cơ chế của Activity là khi bạn chuyển từ một Activity này sáng Activity khác thì nó sẽ Activity đó vào stack, và khi back về thì Activity sẽ được hiện lên lại và chạy vào onResume(), nếu bạn chưa hiểu về  vòng đời của Activity  thì xem lại bài viết kèm video tại blog mình nhé, mình ví dụ khá chi tiết. Intent intent = new Intent(MainActivity.this, LoginActivity.class); startActivity(intent); finish(); Đoạn code trên nghĩa là chuyển từ MainActivity sang LoginActivity và sau đó huỷ luôn MainActivity đúng không nào? Tuy nhiên đó không phải là cách duy nhất mà chúng ta làm đâu bởi Android hỗ trợ chúng ta một số thuộc tính mà bạn không phải dùng code Java để làm. Không lưu Activity vào stack Cũng logic như bài toán phí...