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

Kích thước icon cho app Android và công cụ tạo icon của Google

Để hiển thị chuẩn theo các size màn hình thì chúng ta sẽ theo các kích thước sau: 36 × 36 (ldpi) – Low 48 × 48 (mdpi) – Medium 72 × 72 (hdpi) – High 96 × 96 (x-hdpi) – x-high 144 × 144 (xx-hdpi) 192 × 192 (xxx-hdpi) 512 × 512 (Google Play store) -> Kích thước này để làm ảnh demo cho App khi upload lên store. Khi tạo icon launcher cho app nếu tạo bằng các  Launcher icon generator  cửa Google thì khi cài vào điện thoại nó sẽ bé hơn so với các app khác vì google tự động cho thêm padding vào icon. Tránh điều này thì nên tự thiết kế bằng Photoshop sau đó dùng  cái này  để tạo thì sẽ to và đẹp hơn, nó tạo nhanh và đủ các kích thước chuẩn như bên trên kia. Nếu bạn muốn bo góc thì cũng làm bo góc ở trong photoshop trước sau đó mới dùng công cụ bên trên. Kiến thức liên quan đến đơn vị đo trong Android: pixel có thể hiểu là số điểm ảnh có trong 1 dot có hình vuông vì là ảnh bitmap mà. Ảnh đen trắng binary image thì 1 dot = 1 px = 1 bit (chỉ có trạng thá...

Get You Last known Location & Current Location using FusedLocationProviderClient

NOVEMBER 30, 2017 Get You Last known Location & Current Location using FusedLocationProviderClient        I would like to cover very basic and simple example of retreiving last known location & Current Location  using Fused location API.  I have written many example for fused location API in my previous posts but in this post i will show how to get retrieve the location  without implementing Google Api Client . All these magics happen after the release of version  11.0.0 of Google Play services SDK. FusedLocationProviderApi is Deprecated We have previously used following way to retrieve the last known location. mLastLocation  =  LocationServices. FusedLocationApi . getLastLocation ( mGoogleApiClient ) ; We no need to define LocationServices. FusedLocationApi  anymore. it is deprecated. It will be removed in the future release. Instead You can use LocationServi...

Cấu trúc cơ bản layout trong Flutter