Menyegarkan token OAuth menggunakan Retrofit tanpa mengubah semua panggilan

157

Kami menggunakan Retrofit di aplikasi Android kami, untuk berkomunikasi dengan server aman OAuth2. Semuanya berfungsi dengan baik, kami menggunakan RequestInterceptor untuk memasukkan token akses dengan setiap panggilan. Namun akan ada waktu, ketika token akses akan kedaluwarsa, dan token perlu di-refresh. Saat token kedaluwarsa, panggilan berikutnya akan kembali dengan kode HTTP Tidak Sah, sehingga mudah dipantau. Kita dapat memodifikasi setiap panggilan Retrofit dengan cara berikut: Pada kegagalan panggilan balik, periksa kode kesalahan, jika sama dengan Tidak Sah, segarkan token OAuth, lalu ulangi panggilan Retrofit. Namun, untuk ini, semua panggilan harus dimodifikasi, yang bukan solusi yang mudah dirawat, dan bagus. Apakah ada cara untuk melakukan ini tanpa mengubah semua panggilan Retrofit?

Daniel Zolnai
sumber
1
Ini terlihat relevan dengan pertanyaan saya yang lain . Saya akan memeriksanya lagi segera, tetapi satu pendekatan yang mungkin adalah membungkus OkHttpClient. Sesuatu seperti ini: github.com/pakerfeldt/signpost-retrofit Juga, karena saya menggunakan RoboSpice dengan Retrofit, membuat kelas basis Permintaan mungkin merupakan pendekatan lain yang mungkin juga. Mungkin Anda harus mencari cara untuk mencapai aliran Anda tanpa Konteks, mungkin menggunakan Otto / EventBus.
Hassan Ibraheem
1
Yah Anda bisa memotongnya, dan menghapus kasing yang tidak dibutuhkan. Saya akan melihat ini mungkin hari ini, dan memposting di sini jika saya mencapai sesuatu yang dapat memecahkan masalah kita.
Daniel Zolnai
2
Ternyata, bahwa perpustakaan tidak menangani token yang menyegarkan, tetapi memberi saya ide. Saya membuat intisari kecil tentang beberapa kode yang belum teruji, tetapi secara teori, saya pikir ini akan berhasil: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai
3
@neworld Sebuah solusi yang dapat saya pikirkan: buat perubahanTokenInRequest (...) disinkronkan, dan pada baris pertama, periksa kapan terakhir kali token disegarkan. Jika baru beberapa detik (milidetik) yang lalu, jangan menyegarkan token. Anda juga dapat mengatur jangka waktu ini menjadi 1 jam atau lebih, untuk berhenti terus-menerus meminta token baru ketika ada masalah lain di luar token yang kedaluwarsa.
Daniel Zolnai
2
Retrofit 1.9.0 baru saja menambahkan dukungan untuk OkHttp 2.2, yang memiliki interceptor. Ini akan membuat pekerjaan Anda jauh lebih mudah. Untuk info lebih lanjut, lihat: github.com/square/retrofit/blob/master/… dan github.com/square/okhttp/wiki/Interceptors Anda harus memperluas OkHttp untuk ini juga.
Daniel Zolnai

Jawaban:

213

Harap jangan gunakan Interceptorsuntuk berurusan dengan otentikasi.

Saat ini, pendekatan terbaik untuk menangani otentikasi adalah dengan menggunakan AuthenticatorAPI baru , yang dirancang khusus untuk tujuan ini .

OkHttp akan secara otomatis meminta para Authenticatorkredensial ketika respon yang 401 Not Authorised mencoba kembali permintaan terakhir gagal dengan mereka.

public class TokenAuthenticator implements Authenticator {
    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {
        // Refresh your access_token using a synchronous api request
        newAccessToken = service.refreshToken();

        // Add new header to rejected request and retry it
        return response.request().newBuilder()
                .header(AUTHORIZATION, newAccessToken)
                .build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        // Null indicates no attempt to authenticate.
        return null;
    }

Lampirkan sebuah Authenticatorke cara OkHttpClientyang sama Anda lakukan denganInterceptors

OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);

Gunakan klien ini saat membuat Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);
lgvalle
sumber
6
Apakah ini berarti bahwa setiap permintaan akan selalu gagal 1 kali atau apakah Anda menambahkan token saat melakukan permintaan?
Jdruwe
11
@ Jonruwe sepertinya kode ini akan gagal 1 kali, dan kemudian akan membuat permintaan. namun jika Anda menambahkan interseptor yang hanya bertujuan untuk selalu menambahkan token akses (terlepas apakah itu kadaluwarsa atau tidak) maka ini hanya akan dipanggil ketika 401 diterima yang hanya akan terjadi ketika token itu telah kadaluwarsa.
narciero
54
TokenAuthenticatortergantung suatu servicekelas. The servicekelas tergantung pada OkHttpClientcontoh. Untuk membuat OkHttpClientsaya perlu TokenAuthenticator. Bagaimana saya bisa memutus siklus ini? Dua OkHttpClients berbeda ? Mereka akan memiliki kolam koneksi yang berbeda ...
Brais Gabin
6
Bagaimana dengan banyak permintaan paralel yang perlu menyegarkan token? Akan ada banyak permintaan token penyegaran pada saat bersamaan. Bagaimana cara menghindarinya?
Igor Kostenko
10
Ok, jadi solusi untuk masalah @ Ihor dapat menyinkronkan kode di dalam Authenticator. Ini memecahkan masalah dalam kasus saya. dalam metode Otentikasi Permintaan (...): - lakukan hal-hal initalization - mulailah blok yang disinkronkan (disinkronkan (MyAuthenticator.class) {...}) - di blok itu ambil akses saat ini & token penyegaran - periksa apakah permintaan gagal menggunakan terbaru token akses (resp.request (). header ("Otorisasi")) - jika tidak hanya menjalankannya sekali lagi dengan token akses yang diperbarui - jika tidak, jalankan refresh token flow - perbarui / pertahankan akses yang diperbarui dan segarkan token - selesaikan sinkronisasi blok - jalankan kembali
Dariusz Wiechecki
65

Jika Anda menggunakan Retrofit > = 1.9.0maka Anda dapat menggunakan Interceptor baru OkHttp , yang diperkenalkan di . Anda ingin menggunakan Pencegat Aplikasi , yang memungkinkan Anda untuk melakukannya .OkHttp 2.2.0retry and make multiple calls

Interceptor Anda bisa terlihat seperti kodesemu ini:

public class CustomInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // try the request
        Response response = chain.proceed(request);

        if (response shows expired token) {

            // get a new token (I use a synchronous Retrofit call)

            // create a new request and modify it accordingly using the new token
            Request newRequest = request.newBuilder()...build();

            // retry the request
            return chain.proceed(newRequest);
        }

        // otherwise just pass the original response on
        return response;
    }

}

Setelah Anda menentukan Interceptor, buat OkHttpClientdan tambahkan interseptor sebagai Aplikasi Interceptor .

    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.interceptors().add(new CustomInterceptor());

Dan akhirnya, gunakan ini OkHttpClientsaat membuat RestAdapter.

    RestService restService = new RestAdapter().Builder
            ...
            .setClient(new OkClient(okHttpClient))
            .create(RestService.class);

Peringatan: Seperti yang Jesse Wilsondisebutkan (dari Square) di sini , ini adalah kekuatan yang berbahaya.

Dengan itu, saya pasti berpikir ini adalah cara terbaik untuk menangani sesuatu seperti ini sekarang. Jika Anda memiliki pertanyaan, jangan ragu untuk bertanya dalam komentar.

theblang
sumber
2
Bagaimana Anda mencapai panggilan sinkron di Android ketika Android tidak mengizinkan panggilan jaringan di utas utama? Saya mengalami masalah mengembalikan Respons dari panggilan asinkron.
lgdroid57
1
@ lgdroid57 Anda benar, jadi Anda seharusnya sudah berada di utas lain ketika Anda memulai permintaan awal yang memicu pencegat untuk berjalan.
theblang
3
Ini berfungsi dengan baik kecuali saya harus memastikan untuk menutup respons sebelumnya atau saya akan membocorkan koneksi sebelumnya ... Permintaan akhir newRequest = request.newBuilder () .... build (); response.body (). close (); return chain.proceed (newRequest);
DallinDyer
Terima kasih! Saya mengalami masalah di mana Callback dari permintaan asli menerima pesan kesalahan "ditutup" bukan respons asli, karena tubuh yang dikonsumsi di Interceptor. Saya dapat memperbaiki ini untuk tanggapan yang berhasil, tetapi saya tidak dapat memperbaikinya untuk tanggapan yang gagal. Ada saran?
lgdroid57
Terima kasih @mblblang, ini terlihat bagus. Satu pertanyaan: apakah permintaan panggilan balik dijamin akan dipanggil bahkan pada coba lagi?
Luca Fagioli
23

TokenAuthenticator bergantung pada kelas layanan. Kelas layanan tergantung pada instance OkHttpClient. Untuk membuat OkHttpClient, saya memerlukan TokenAuthenticator. Bagaimana saya bisa memutus siklus ini? Dua OkHttpClients berbeda? Mereka akan memiliki kolam koneksi yang berbeda ..

Jika Anda memiliki, katakanlah, Retrofit TokenServiceyang Anda butuhkan di dalam Anda Authenticatortetapi Anda hanya ingin mengaturnya, OkHttpClientAnda dapat menggunakannya TokenServiceHoldersebagai ketergantungan TokenAuthenticator. Anda harus mempertahankan referensi untuk itu di tingkat aplikasi (tunggal). Ini mudah jika Anda menggunakan Dagger 2, jika tidak, buat saja bidang kelas di dalam Aplikasi Anda.

Di TokenAuthenticator.java

public class TokenAuthenticator implements Authenticator {

    private final TokenServiceHolder tokenServiceHolder;

    public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
        this.tokenServiceHolder = tokenServiceHolder;
    }

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {

        //is there a TokenService?
        TokenService service = tokenServiceHolder.get();
        if (service == null) {
            //there is no way to answer the challenge
            //so return null according to Retrofit's convention
            return null;
        }

        // Refresh your access_token using a synchronous api request
        newAccessToken = service.refreshToken().execute();

        // Add new header to rejected request and retry it
        return response.request().newBuilder()
                .header(AUTHORIZATION, newAccessToken)
                .build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        // Null indicates no attempt to authenticate.
        return null;
    }

Di TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

    @Nullable
    public TokenService get() {
        return tokenService;
    }

    public void set(TokenService tokenService) {
        this.tokenService = tokenService;
    }
}

Pengaturan klien:

//obtain instance of TokenServiceHolder from application or singleton-scoped component, then
TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder);
OkHttpClient okHttpClient = new OkHttpClient();    
okHttpClient.setAuthenticator(tokenAuthenticator);

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .client(okHttpClient)
    .build();

TokenService tokenService = retrofit.create(TokenService.class);
tokenServiceHolder.set(tokenService);

Jika Anda menggunakan Dagger 2 atau kerangka kerja injeksi ketergantungan serupa, ada beberapa contoh dalam jawaban untuk pertanyaan ini

David Rawson
sumber
Di mana TokenServicekelas dibuat?
Yogesh Suthar
@YogeshSuthar ini layanan Retrofit - lihat pertanyaan terkait
David Rawson
Terima kasih, dapatkah Anda memberikan implementasi refreshToken()dari service.refreshToken().execute();. Tidak dapat menemukan penerapannya di mana pun.
Yogesh Suthar
@Yogesh Metode refreshToken berasal dari API Anda. Apa pun yang Anda panggil untuk menyegarkan token (panggilan dengan nama pengguna dan kata sandi mungkin?). Atau mungkin permintaan tempat Anda mengirimkan token dan responsnya adalah token baru
David Rawson
5

Menggunakan TokenAuthenticatorlike @theblang answer adalah cara yang tepat untuk menangani refresh_token.

Inilah alat saya (saya telah menggunakan Kotlin, Dagger, RX tetapi Anda dapat menggunakan ide ini untuk alat untuk kasus Anda)
TokenAuthenticator

class TokenAuthenticator @Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {

    override fun authenticate(route: Route, response: Response): Request? {
        val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
        accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
        return response.request().newBuilder()
                .header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
                .build()
    }
}

Untuk mencegah siklus ketergantungan seperti komentar @Brais Gabin, saya membuat 2 antarmuka seperti

interface PotoNoneAuthApi { // NONE authentication API
    @POST("/login")
    fun login(@Body request: LoginRequest): Single<AccessToken>

    @POST("refresh_token")
    @FormUrlEncoded
    fun refreshToken(@Field("refresh_token") refreshToken: String): Single<AccessToken>
}

dan

interface PotoAuthApi { // Authentication API
    @GET("api/images")
    fun getImage(): Single<GetImageResponse>
}

AccessTokenWrapper kelas

class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
    private var accessToken: AccessToken? = null

    // get accessToken from cache or from SharePreference
    fun getAccessToken(): AccessToken? {
        if (accessToken == null) {
            accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
        }
        return accessToken
    }

    // save accessToken to SharePreference
    fun saveAccessToken(accessToken: AccessToken) {
        this.accessToken = accessToken
        sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
    }
}

AccessToken kelas

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

Pencegat saya

class AuthInterceptor @Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val authorisedRequestBuilder = originalRequest.newBuilder()
                .addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
                .header("Accept", "application/json")
        return chain.proceed(authorisedRequestBuilder.build())
    }
}

Akhirnya, tambahkan Interceptordan Authenticatorke OKHttpClientlayanan PotoAuthApi Anda saat membuat

Demo

https://github.com/PhanVanLinh/AndroidMVPKotlin

Catatan

Aliran Authenticator
  • Contoh API getImage()mengembalikan 401 kode kesalahan
  • authenticate metode di dalam TokenAuthenticator akan dipecat
  • Sinkronkan noneAuthAPI.refreshToken(...) disebut
  • Setelah noneAuthAPI.refreshToken(...) respons -> token baru akan ditambahkan ke header
  • getImage()AUTO akan dipanggil dengan tajuk baru ( HttpLogging TIDAK AKAN mencatat panggilan ini) ( interceptdi dalamAuthInterceptor TIDAK AKAN DISEBUT )
  • Jika getImage()masih gagal dengan kesalahan 401, authenticatemetode di dalamnya TokenAuthenticatorakan menembakkan AGAIN dan AGAIN kemudian akan membuang kesalahan tentang metode panggilan berkali-kali ( java.net.ProtocolException: Too many follow-up requests). Anda dapat mencegahnya dengan menghitung tanggapan . Contoh, jika Anda return nullmasuk authenticatesetelah 3 kali coba lagi, getImage()akan selesai danreturn response 401

  • Jika getImage()respons berhasil => kami akan menghasilkan hasilnya secara normal (seperti Anda menelepongetImage() tanpa kesalahan)

Semoga ini bisa membantu

Phan Van Linh
sumber
Solusi ini menggunakan 2 OkHttpClients berbeda, seperti terbukti dalam kelas ServiceGenerator Anda.
SpecialSnowflake
@SpecialSnowflake Anda benar. Jika Anda mengikuti solusi saya, Anda perlu membuat 2 OkHttp karena saya membuat 2 layanan (oauth dan tidak ada auth). Saya pikir itu tidak akan menimbulkan masalah. Biarkan saya tahu ide Anda
Phan Van Linh
1

Saya tahu ini utas lama, tapi kalau-kalau ada yang tersandung.

TokenAuthenticator bergantung pada kelas layanan. Kelas layanan tergantung pada instance OkHttpClient. Untuk membuat OkHttpClient, saya memerlukan TokenAuthenticator. Bagaimana saya bisa memutus siklus ini? Dua OkHttpClients berbeda? Mereka akan memiliki kolam koneksi yang berbeda ..

Saya menghadapi masalah yang sama, tetapi saya hanya ingin membuat satu OkHttpClient karena saya tidak berpikir saya perlu yang lain hanya untuk TokenAuthenticator itu sendiri, saya menggunakan Dagger2, jadi saya akhirnya menyediakan kelas layanan ketika Lazy disuntikkan ke dalam TokenAuthenticator, Anda dapat membaca lebih lanjut tentang injeksi Malas di belati 2 di sini , tapi itu seperti pada dasarnya mengatakan kepada Belati untuk TIDAK pergi dan membuat layanan yang dibutuhkan oleh TokenAuthenticator segera.

Anda dapat merujuk ke utas SO ini untuk kode sampel: Bagaimana cara mengatasi ketergantungan melingkar saat masih menggunakan Dagger2?

Boda
sumber
0

Anda dapat mencoba membuat kelas dasar untuk semua loader Anda di mana Anda dapat menangkap pengecualian tertentu dan kemudian bertindak sesuai kebutuhan. Buat semua loader Anda yang berbeda diperluas dari kelas dasar untuk menyebarkan perilaku.

k3v1n4ud3
sumber
Retrofit tidak berfungsi seperti itu. Menggunakan anotasi java, dan antarmuka untuk menggambarkan panggilan API
Daniel Zolnai
Saya tahu cara kerja retrofit, tetapi Anda masih "membungkus" panggilan API Anda di dalam AsynTask bukan?
k3v1n4ud3
Tidak, saya menggunakan panggilan dengan Callback, sehingga mereka berjalan secara tidak sinkron.
Daniel Zolnai
Maka Anda mungkin dapat membuat kelas panggilan balik dasar dan membuat semua panggilan balik Anda memperpanjangnya.
k3v1n4ud3
2
Ada solusi untuk ini? Persis seperti kasus saya di sini. = /
Hugo Nogueira
0

Setelah penelitian panjang, saya mengkustomisasi klien Apache untuk menangani Refreshing AccessToken Untuk Retrofit Di mana Anda mengirim token akses sebagai parameter.

Memulai Adaptor Anda dengan Cookie Persistent Client

restAdapter = new RestAdapter.Builder()
                .setEndpoint(SERVER_END_POINT)
                .setClient(new CookiePersistingClient())
                .setLogLevel(RestAdapter.LogLevel.FULL).build();

Cookie Persistent client yang mengelola cookie untuk semua permintaan dan memeriksa dengan setiap respons permintaan, jika itu akses tidak sah ERROR_CODE = 401, menyegarkan token akses dan memanggil kembali permintaan, jika tidak hanya memproses permintaan.

private static class CookiePersistingClient extends ApacheClient {

    private static final int HTTPS_PORT = 443;
    private static final int SOCKET_TIMEOUT = 300000;
    private static final int CONNECTION_TIMEOUT = 300000;

    public CookiePersistingClient() {
        super(createDefaultClient());
    }

    private static HttpClient createDefaultClient() {
        // Registering https clients.
        SSLSocketFactory sf = null;
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore
                    .getDefaultType());
            trustStore.load(null, null);

            sf = new MySSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(params,
                CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("https", sf, HTTPS_PORT));
        // More customization (https / timeouts etc) can go here...

        ClientConnectionManager cm = new ThreadSafeClientConnManager(
                params, registry);
        DefaultHttpClient client = new DefaultHttpClient(cm, params);

        // Set the default cookie store
        client.setCookieStore(COOKIE_STORE);

        return client;
    }

    @Override
    protected HttpResponse execute(final HttpClient client,
            final HttpUriRequest request) throws IOException {
        // Set the http context's cookie storage
        BasicHttpContext mHttpContext = new BasicHttpContext();
        mHttpContext.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE);
        return client.execute(request, mHttpContext);
    }

    @Override
    public Response execute(final Request request) throws IOException {
        Response response = super.execute(request);
        if (response.getStatus() == 401) {

            // Retrofit Callback to handle AccessToken
            Callback<AccessTockenResponse> accessTokenCallback = new Callback<AccessTockenResponse>() {

                @SuppressWarnings("deprecation")
                @Override
                public void success(
                        AccessTockenResponse loginEntityResponse,
                        Response response) {
                    try {
                        String accessToken =  loginEntityResponse
                                .getAccessToken();
                        TypedOutput body = request.getBody();
                        ByteArrayOutputStream byte1 = new ByteArrayOutputStream();
                        body.writeTo(byte1);
                        String s = byte1.toString();
                        FormUrlEncodedTypedOutput output = new FormUrlEncodedTypedOutput();
                        String[] pairs = s.split("&");
                        for (String pair : pairs) {
                            int idx = pair.indexOf("=");
                            if (URLDecoder.decode(pair.substring(0, idx))
                                    .equals("access_token")) {
                                output.addField("access_token",
                                        accessToken);
                            } else {
                                output.addField(URLDecoder.decode(
                                        pair.substring(0, idx), "UTF-8"),
                                        URLDecoder.decode(
                                                pair.substring(idx + 1),
                                                "UTF-8"));
                            }
                        }
                        execute(new Request(request.getMethod(),
                                request.getUrl(), request.getHeaders(),
                                output));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }

                @Override
                public void failure(RetrofitError error) {
                    // Handle Error while refreshing access_token
                }
            };
            // Call Your retrofit method to refresh ACCESS_TOKEN
            refreshAccessToken(GRANT_REFRESH,CLIENT_ID, CLIENT_SECRET_KEY,accessToken, accessTokenCallback);
        }

        return response;
    }
}
Suneel Prakash
sumber
Apakah ada alasan mengapa Anda menggunakan ApacheClient daripada solusi yang disarankan? Bukannya itu bukan solusi yang baik, tetapi membutuhkan lebih banyak pengkodean, dibandingkan dengan menggunakan Interceptors.
Daniel Zolnai
Itu disesuaikan untuk menjadi klien tetap cookie, mempertahankan sesi di seluruh layanan. Bahkan di Peminta Interkeptor, Anda dapat menambahkan akses ke dalam header. Tetapi bagaimana jika Anda ingin menambahkannya sebagai param? OKHTTPClient juga memiliki keterbatasan. ref: stackoverflow.com/questions/24594823/…
Suneel Prakash
Ini lebih digeneralisasi untuk digunakan dalam hal apa pun 1. Cookie Persistent Client 2. Menerima permintaan HTTP dan HTTPS 3. Memperbarui Token Akses di Params.
Suneel Prakash
0

Menggunakan satu Interceptor (menyuntikkan token) dan satu Authenticator (operasi penyegaran) melakukan pekerjaan tetapi:

Saya punya masalah panggilan ganda juga: panggilan pertama selalu mengembalikan 401 : token tidak menyuntikkan pada panggilan pertama (pencegat) dan otentikasiator dipanggil: dua permintaan dibuat.

Cara mengatasinya hanya untuk mengaktifkan kembali permintaan ke build di Interceptor:

SEBELUM:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request();
        //...
        request.newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

SETELAH:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request();
        //...
        request = request.newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

DALAM SATU BLOK:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request().newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

Semoga ini bisa membantu.

Sunting: Saya tidak menemukan cara untuk menghindari panggilan pertama untuk selalu mengembalikan 401 hanya menggunakan autentikator dan tidak ada pencegat

Sigrun
sumber
-2

Untuk siapa saja yang ingin menyelesaikan panggilan bersamaan / paralel saat menyegarkan token. Ini solusinya

class TokenAuthenticator: Authenticator {

    override fun authenticate(route: Route?, response: Response?): Request? {
        response?.let {
            if (response.code() == 401) {
                while (true) {
                    if (!isRefreshing) {
                        val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION)
                        val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token)

                        currentToken?.let {
                            if (requestToken != currentToken) {
                                return generateRequest(response, currentToken)
                            }
                        }

                        val token = refreshToken()
                        token?.let {
                            return generateRequest(response, token)
                        }
                    }
                }
            }
        }

        return null
    }

    private fun generateRequest(response: Response, token: String): Request? {
        return response.request().newBuilder()
                .header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA)
                .header(AuthorisationInterceptor.AUTHORISATION, token)
                .build()
    }

    private fun refreshToken(): String? {
        synchronized(TokenAuthenticator::class.java) {
            UserService.instance.token?.let {
                isRefreshing = true

                val call = ApiHelper.refreshToken()
                val token = call.execute().body()
                UserService.instance.setToken(token, false)

                isRefreshing = false

                return OkHttpUtil.headerBuilder(token)
            }
        }

        return null
    }

    companion object {
        var isRefreshing = false
    }
}
Anders Cheow
sumber