Odświeżanie tokenu OAuth przy użyciu funkcji Retrofit bez modyfikowania wszystkich wywołań

157

Używamy Retrofit w naszej aplikacji na Androida, aby komunikować się z serwerem zabezpieczonym OAuth2. Wszystko działa świetnie, używamy RequestInterceptor do dołączania tokenu dostępu do każdego wywołania. Jednak zdarzają się sytuacje, w których token dostępu wygaśnie i będzie wymagał odświeżenia. Po wygaśnięciu tokenu następne wywołanie zwróci z nieautoryzowanym kodem HTTP, więc jest to łatwe do monitorowania. Moglibyśmy zmodyfikować każde wywołanie Retrofit w następujący sposób: W wywołaniu zwrotnym błędu, sprawdź kod błędu, jeśli jest równy Unauthorized, odśwież token OAuth, a następnie powtórz wywołanie Retrofit. Jednak w tym celu wszystkie wywołania powinny zostać zmodyfikowane, co nie jest łatwym w utrzymaniu i dobrym rozwiązaniem. Czy można to zrobić bez modyfikowania wszystkich wywołań dotyczących modernizacji?

Daniel Zolnai
źródło
1
To wygląda na odpowiednie dla mojego drugiego pytania . Wkrótce zajrzę do tego ponownie, ale jednym z możliwych sposobów jest opakowanie OkHttpClient. Coś takiego: github.com/pakerfeldt/signpost-retrofit Ponadto, ponieważ używam RoboSpice z Retrofit, utworzenie podstawowej klasy Request może być również innym możliwym podejściem. Prawdopodobnie będziesz musiał dowiedzieć się, jak osiągnąć swój przepływ bez kontekstu, być może za pomocą Otto / EventBus.
Hassan Ibraheem
1
Cóż, możesz go rozwidlić i usunąć niepotrzebne skrzynki. Może dzisiaj przyjrzę się temu i opublikuję tutaj, jeśli osiągnąłem coś, co mogłoby rozwiązać nasz problem.
Daniel Zolnai,
2
Okazało się, że biblioteka nie obsługuje odświeżania tokenów, ale podsunęła mi pomysł. Zrobiłem krótkie podsumowanie o niektórych! Nieprzetestowany kod, ale teoretycznie myślę, że powinien działać: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai
3
@neworld Rozwiązanie, które przychodzi mi do głowy: zsynchronizuj changeTokenInRequest (...), aw pierwszym wierszu sprawdź, kiedy ostatnio token był odświeżany. Jeśli minęło zaledwie kilka sekund (milisekund) temu, nie odświeżaj tokenu. Możesz również ustawić ten przedział czasowy na około 1 godzinę, aby przestać żądać nowych tokenów, gdy występuje inny problem poza tokenem, który jest nieaktualny.
Daniel Zolnai
2
Retrofit 1.9.0 właśnie dodał obsługę OkHttp 2.2, który ma przechwytywacze. Powinno to znacznie ułatwić ci pracę. Aby uzyskać więcej informacji, zobacz: github.com/square/retrofit/blob/master/… i github.com/square/okhttp/wiki/Interceptors Musisz jednak rozszerzyć OkHttp również dla nich.
Daniel Zolnai

Odpowiedzi:

213

Nie używaj go Interceptorsdo uwierzytelniania.

Obecnie najlepszym podejściem do obsługi uwierzytelniania jest użycie nowego Authenticatorinterfejsu API, zaprojektowanego specjalnie do tego celu .

OkHttp zostanie automatycznie zapytaćAuthenticator o poświadczenia, gdy odpowiedź jest 401 Not Authorised Ponowna próba nie powiodła się ostatni wniosek z nimi.

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

Dołącz Authenticatordo w OkHttpClienttaki sam sposób, w jaki robisz to zInterceptors

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

Użyj tego klienta podczas tworzenia Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);
lgvalle
źródło
6
Czy to oznacza, że ​​każde żądanie zawodzi zawsze 1 raz, czy też dodajesz token podczas wykonywania żądań?
Jdruwe,
11
@Jdruwe wygląda na to, że ten kod zawiedzie 1 raz, a następnie wykona żądanie. jednak jeśli dodasz przechwytywacz, którego jedynym celem jest zawsze dodawanie tokenu dostępu (niezależnie od tego, czy wygasł, czy nie), zostanie on wywołany tylko po odebraniu 401, co nastąpi tylko wtedy, gdy ten token wygaśnie.
narciero
54
TokenAuthenticatorzależy od serviceklasy. serviceKlasy zależy od OkHttpClientinstancji. Aby utworzyć plik OkHttpClient, potrzebuję TokenAuthenticator. Jak mogę przerwać ten cykl? Dwa różne OkHttpClient? Będą mieli różne pule połączeń ...
Brais Gabin
6
A co z wieloma równoległymi żądaniami, które wymagają odświeżenia tokenu? Będzie to jednocześnie wiele żądań odświeżenia tokena. Jak tego uniknąć?
Igor Kostenko
10
Ok, więc rozwiązaniem problemu @ Ihor może być synchronizacja kodu wewnątrz Authenticatora. To rozwiązało problem w moim przypadku. w metodzie Request Authenticate (...): - wykonaj dowolną inicjalizację - rozpocznij synchronizowany blok (zsynchronizowany (MyAuthenticator.class) {...}) - w tym bloku pobierz aktualny dostęp i odśwież token - sprawdź, czy nieudane żądanie używa najnowszego access token (resp.request (). header ("Authorization")) - jeśli nie po prostu uruchom go ponownie ze zaktualizowanym tokenem dostępu - w przeciwnym razie uruchom odśwież przepływ tokenu - zaktualizuj / utrwal zaktualizowany dostęp i odśwież token - zakończ zsynchronizowany blok - uruchom ponownie
Dariusz Wiechecki
65

Jeśli używasz Retrofit > =, 1.9.0możesz skorzystać z nowego Interceptora OkHttp , który został wprowadzony w . Chciałbyś użyć Application Interceptor , który na to pozwala .OkHttp 2.2.0retry and make multiple calls

Twój Interceptor może wyglądać mniej więcej tak, jak ten pseudokod:

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

}

Po zdefiniowaniu Interceptorutwórz OkHttpClienti dodaj przechwytywacz jako element przechwytujący aplikacji .

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

I na koniec użyj tego OkHttpClientpodczas tworzenia swojego RestAdapter.

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

Ostrzeżenie: jak Jesse Wilsonwspomina (z Square) tutaj , jest to niebezpieczna ilość mocy.

Mając to na uwadze, zdecydowanie uważam, że to najlepszy sposób na teraz poradzenie sobie z czymś takim. Jeśli masz jakieś pytania, nie wahaj się zadać w komentarzu.

theblang
źródło
2
Jak uzyskujesz połączenie synchroniczne w systemie Android, gdy system Android nie zezwala na połączenia sieciowe w głównym wątku? Mam problemy ze zwracaniem odpowiedzi z wywołania asynchronicznego.
lgdroid57
1
@ lgdroid57 Masz rację, więc powinieneś być już w innym wątku, kiedy uruchomiłeś pierwotne żądanie, które spowodowało uruchomienie przechwytywacza.
theblang
3
To działało świetnie, z wyjątkiem tego, że musiałem się upewnić, że poprzednia odpowiedź została zamknięta, w przeciwnym razie poprzednie połączenie wyciekłoby ... final Żądanie newRequest = request.newBuilder () .... build (); response.body (). close (); return chain.proceed (newRequest);
DallinDyer
Dzięki! Wystąpił problem polegający na tym, że wywołanie zwrotne pierwotnego żądania otrzymywało komunikat o błędzie „zamknięty” zamiast oryginalnej odpowiedzi, ponieważ ciało jest zużywane w module przechwytującym. Udało mi się to naprawić w przypadku udanych odpowiedzi, ale nie mogłem tego naprawić w przypadku nieudanych odpowiedzi. Jakieś sugestie?
lgdroid57
Dzięki @mattblang, wygląda ładnie. Jedno pytanie: czy żądanie oddzwonienia zostanie wywołane nawet przy ponownej próbie?
Luca Fagioli
23

TokenAuthenticator zależy od klasy usługi. Klasa usługi zależy od instancji OkHttpClient. Aby utworzyć OkHttpClient, potrzebuję TokenAuthenticator. Jak mogę przerwać ten cykl? Dwóch różnych klientów OkHttp? Będą mieć różne pule połączeń.

Jeśli masz, powiedzmy, Retrofit TokenService, którego potrzebujesz w swoim, Authenticatorale chciałbyś skonfigurować tylko taki, dla OkHttpClientktórego możesz użyć TokenServiceHolderjako zależności TokenAuthenticator. Musiałbyś zachować odniesienie do niego na poziomie aplikacji (singleton). Jest to łatwe, jeśli używasz Daggera 2, w przeciwnym razie po prostu utwórz pole klasy w aplikacji.

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

W TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

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

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

Konfiguracja klienta:

//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);

Jeśli używasz Daggera 2 lub podobnego frameworka wstrzykiwania zależności, w odpowiedziach na to pytanie znajduje się kilka przykładów

David Rawson
źródło
Gdzie jest TokenServicetworzona klasa?
Yogesh Suthar
@YogeshSuthar to usługa modernizacji - zobacz powiązane pytanie
David Rawson
Dzięki, czy możesz zapewnić implementację refreshToken()from service.refreshToken().execute();. Nigdzie nie mogę znaleźć jego implementacji.
Yogesh Suthar
@Yogesh Metoda refreshToken pochodzi z twojego API. Cokolwiek wywołasz, aby odświeżyć token (może połączenie z nazwą użytkownika i hasłem?). A może prośba, w której przesyłasz token, a odpowiedzią jest nowy token
David Rawson
5

Użycie TokenAuthenticatorodpowiedzi like @theblang jest poprawnym sposobem obsługi refresh_token.

Oto moje narzędzie (ja używam Kotlin, Dagger, RX ale możesz wykorzystać ten pomysł na implementację do swojego przypadku)
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()
    }
}

Aby zapobiec cyklowi zależności, jak komentarz @Brais Gabin, tworzę 2 interfejsy, takie jak

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

i

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

AccessTokenWrapper klasa

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 klasa

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

My Interceptor

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())
    }
}

Na koniec dodaj Interceptori Authenticatordo swojego OKHttpClientpodczas tworzenia usługi PotoAuthApi

Próbny

https://github.com/PhanVanLinh/AndroidMVPKotlin

Uwaga

Przepływ uwierzytelniania
  • Przykładowy getImage()kod błędu API zwrócił 401
  • authenticateMetoda wewnątrz TokenAuthenticatorzostanie zwolniona
  • Synchronizuj noneAuthAPI.refreshToken(...)wywołane
  • Po noneAuthAPI.refreshToken(...)odpowiedzi -> nowy token zostanie dodany do nagłówka
  • getImage()wywoła AUTO z nowym nagłówkiem ( HttpLogging NIE BĘDZIE zarejestrować tego połączenia) ( interceptwewnątrz AuthInterceptor NIE BĘDZIE WYWOŁANE )
  • Jeśli getImage()nadal nie powiodło się z błędem 401, authenticatemetoda wewnętrzna TokenAuthenticatorzostanie uruchomiona PONOWNIE i PONOWNIE, a następnie wiele razy ( java.net.ProtocolException: Too many follow-up requests) wyrzuci błąd dotyczący wywołania metody . Możesz temu zapobiec, licząc odpowiedź . Przykładowo, jeśli return nullw authenticatepo 3 razy powtórzyć, getImage()będzie skończyć ireturn response 401

  • Jeśli getImage()odpowiedź zakończy się sukcesem => otrzymamy wynik normalnie (jak dzwonisz getImage()bez błędu)

Mam nadzieję, że to pomoże

Phan Van Linh
źródło
To rozwiązanie wykorzystuje 2 różne OkHttpClients, jak widać w klasie ServiceGenerator.
SpecialSnowflake
@SpecialSnowflake masz rację. Jeśli podążasz za moim rozwiązaniem, musisz utworzyć 2 OkHttp, ponieważ utworzyłem 2 usługi (oauth i none auth). Myślę, że nie spowoduje to żadnego problemu. Daj mi znać swój pomysł
Phan Van Linh,
1

Wiem, że to stara nitka, ale na wszelki wypadek, gdyby ktoś się w nią potknął.

TokenAuthenticator zależy od klasy usługi. Klasa usługi zależy od instancji OkHttpClient. Aby utworzyć OkHttpClient, potrzebuję TokenAuthenticator. Jak mogę przerwać ten cykl? Dwóch różnych klientów OkHttp? Będą mieć różne pule połączeń.

Miałem ten sam problem, ale chciałem utworzyć tylko jednego OkHttpClient, ponieważ nie sądzę, że potrzebuję innego tylko dla samego TokenAuthenticator, używałem Dagger2, więc ostatecznie zapewniłem klasę usługi jako Lazy wstrzykniętą w TokenAuthenticator, możesz przeczytać więcej o leniwym wstrzyknięciu w sztylet 2 tutaj , ale w zasadzie to tak, jakbyś powiedział Daggerowi, aby NIE iść i od razu tworzyć usługę potrzebną TokenAuthenticator.

Możesz odwołać się do tego wątku SO po przykładowy kod: Jak rozwiązać zależność cykliczną, nadal używając Dagger2?

Boda
źródło
0

Możesz spróbować utworzyć klasę bazową dla wszystkich programów ładujących, w której byłbyś w stanie złapać określony wyjątek, a następnie działać tak, jak potrzebujesz. Spraw, aby wszystkie różne programy ładujące rozszerzały się z klasy bazowej, aby rozpowszechnić zachowanie.

k3v1n4ud3
źródło
Modernizacja nie działa w ten sposób. Używa adnotacji java i interfejsów do opisania wywołania API
Daniel Zolnai
Wiem, jak działa modernizacja, ale nadal „opakowujesz” wywołania interfejsu API wewnątrz AsynTask, prawda?
k3v1n4ud3
Nie, używam wywołań z Callback, więc działają asynchronicznie.
Daniel Zolnai,
Wtedy prawdopodobnie możesz utworzyć podstawową klasę wywołań zwrotnych i sprawić, by wszystkie twoje wywołania zwrotne ją rozszerzały.
k3v1n4ud3
2
Jakieś rozwiązanie tego? To jest dokładnie moja sprawa. = /
Hugo Nogueira
0

Po długich badaniach dostosowałem klienta Apache do obsługi odświeżania AccessToken For Retrofit, w którym wysyłasz token dostępu jako parametr.

Zainicjuj swój Adapter za pomocą trwałego klienta cookie

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

Cookie Trwały klient, który przechowuje pliki cookie dla wszystkich żądań i sprawdza przy każdej odpowiedzi na żądanie, jeśli jest to nieautoryzowany dostęp ERROR_CODE = 401, odświeża token dostępu i przywołuje żądanie, w przeciwnym razie tylko przetwarza żądanie.

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
źródło
Czy jest jakiś powód, dla którego używasz ApacheClient zamiast sugerowanego rozwiązania? Nie żeby to nie było dobre rozwiązanie, ale wymaga dużo więcej kodowania w porównaniu do używania Interceptorów.
Daniel Zolnai
Jest dostosowany do trwałego klienta plików cookie, utrzymuje sesję w całej usłudze. Nawet w module Request Intercceptor możesz dodać accesstoken w nagłówkach. Ale co, jeśli chcesz dodać go jako parametr? Również OKHTTPClient ma ograniczenia. ref: stackoverflow.com/questions/24594823/…
Suneel Prakash
W każdym przypadku jest bardziej uogólniony. 1. Cookie Persistent Client 2. Akceptuje żądania HTTP i HTTPS 3. Aktualizuj Access Token w Params.
Suneel Prakash
0

Używając jednego Interceptora (wstrzyknij token) i jednego Authenticatora (operacje odświeżania) wykonują zadanie, ale:

Miałem też problem z podwójnym połączeniem: pierwsze połączenie zawsze zwracało 401 : token nie został wstrzyknięty przy pierwszym wywołaniu (przechwytywacz) i został wywołany element uwierzytelniający: wykonano dwa żądania.

Poprawka polegała tylko na ponownym wpłynięciu żądania na kompilację w Interceptorze:

PRZED:

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

PO:

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

W JEDNYM BLOKU:

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

Mam nadzieję, że to pomoże.

Edycja: nie znalazłem sposobu, aby uniknąć pierwszego wywołania zawsze zwracającego 401 przy użyciu tylko uwierzytelniacza i bez przechwytywacza

Sigrun
źródło
-2

Do każdego, kto chciał rozwiązać współbieżne / równoległe wywołania podczas odświeżania tokenu. Oto obejście

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
źródło