Aktualisieren des OAuth-Tokens mithilfe von Retrofit, ohne alle Aufrufe zu ändern

157

Wir verwenden Retrofit in unserer Android-App, um mit einem OAuth2-gesicherten Server zu kommunizieren. Alles funktioniert hervorragend. Wir verwenden den RequestInterceptor, um das Zugriffstoken bei jedem Aufruf einzuschließen. Es wird jedoch Zeiten geben, in denen das Zugriffstoken abläuft und das Token aktualisiert werden muss. Wenn das Token abläuft, wird der nächste Aufruf mit einem nicht autorisierten HTTP-Code zurückgegeben, sodass die Überwachung einfach ist. Wir können jeden Retrofit-Aufruf folgendermaßen ändern: Überprüfen Sie im Fehlerrückruf, ob der Fehlercode Unauthorized entspricht, aktualisieren Sie das OAuth-Token und wiederholen Sie den Retrofit-Aufruf. Zu diesem Zweck sollten jedoch alle Anrufe geändert werden, was keine leicht zu wartende und gute Lösung ist. Gibt es eine Möglichkeit, dies zu tun, ohne alle Retrofit-Aufrufe zu ändern?

Daniel Zolnai
quelle
1
Dies scheint für meine andere Frage relevant zu sein . Ich werde es bald noch einmal untersuchen, aber ein möglicher Ansatz ist das Umschließen von OkHttpClient. Etwa so: github.com/pakerfeldt/signpost-retrofit Da ich RoboSpice mit Retrofit verwende, kann das Erstellen einer Basisanforderungsklasse auch ein anderer möglicher Ansatz sein. Wahrscheinlich müssen Sie herausfinden, wie Sie Ihren Flow ohne Kontext erreichen können, vielleicht mit Otto / EventBus.
Hassan Ibraheem
1
Nun, Sie könnten es gabeln und die nicht benötigten Fälle entfernen. Ich werde das vielleicht heute untersuchen und hier posten, wenn ich etwas erreicht habe, das unser Problem lösen könnte.
Daniel Zolnai
2
Es stellte sich heraus, dass die Bibliothek keine erfrischenden Token handhabte, aber mir eine Idee gab. Ich habe einen kleinen Überblick über einige! Ungetestete Codes gemacht, aber theoretisch denke ich, dass es funktionieren sollte: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai
3
@neworld Eine Lösung, die mir einfällt: Synchronisieren Sie changeTokenInRequest (...) und überprüfen Sie in der ersten Zeile, wann das Token das letzte Mal aktualisiert wurde. Aktualisieren Sie das Token nicht, wenn es erst einige Sekunden (Millisekunden) her ist. Sie können diesen Zeitraum auch auf ungefähr 1 Stunde festlegen, um nicht mehr ständig neue Token anzufordern, wenn ein anderes Problem außerhalb des veralteten Tokens auftritt.
Daniel Zolnai
2
Retrofit 1.9.0 hat gerade die Unterstützung für OkHttp 2.2 hinzugefügt, das Interceptors enthält. Dies sollte Ihre Arbeit viel einfacher machen. Weitere Informationen finden Sie unter: github.com/square/retrofit/blob/master/… und github.com/square/okhttp/wiki/Interceptors Sie müssen OkHttp jedoch auch für diese erweitern.
Daniel Zolnai

Antworten:

213

Bitte nicht verwenden Interceptors, um mit der Authentifizierung umzugehen.

Derzeit ist der beste Ansatz für die Authentifizierung die Verwendung der neuen AuthenticatorAPI, die speziell für diesen Zweck entwickelt wurde .

OkHttp fragt automatisch nach den AuthenticatorAnmeldeinformationen, wenn eine Antwort die 401 Not Authorised letzte fehlgeschlagene Anforderung mit ihnen wiederholt .

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

Befestigen Sie ein Authenticatoran einem, OkHttpClientwie Sie es tunInterceptors

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

Verwenden Sie diesen Client beim Erstellen Ihres Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);
lgvalle
quelle
5
Bedeutet dies, dass jede Anforderung immer einmal fehlschlägt, oder fügen Sie das Token hinzu, wenn Sie Anforderungen ausführen?
Jdruwe
11
@Jdruwe es sieht so aus, als würde dieser Code 1 Mal fehlschlagen, und dann wird die Anfrage gestellt. Wenn Sie jedoch einen Interceptor hinzufügen, dessen einziger Zweck darin besteht, immer ein Zugriffstoken hinzuzufügen (unabhängig davon, ob es abgelaufen ist oder nicht), wird dies nur aufgerufen, wenn ein 401 empfangen wird, das nur auftritt, wenn dieses Token abgelaufen ist.
Narciero
54
TokenAuthenticatorhängt von einer serviceKlasse ab. Die serviceKlasse hängt von einer OkHttpClientInstanz ab. Um ein zu erstellen, OkHttpClientbrauche ich das TokenAuthenticator. Wie kann ich diesen Zyklus durchbrechen? Zwei verschiedene OkHttpClients? Sie werden verschiedene Verbindungspools haben ...
Brais Gabin
6
Wie wäre es mit vielen parallelen Anforderungen, die das Token aktualisieren müssen? Es werden viele Aktualisierungstoken gleichzeitig angefordert. Wie vermeide ich das?
Igor Kostenko
10
Ok, die Lösung für das Problem von @ Ihor könnte darin bestehen, den Code in Authenticator zu synchronisieren. Es hat das Problem in meinem Fall gelöst. in Anforderungsauthentifizierungsmethode (...): - Führen Sie alle Initialisierungsaufgaben aus - Starten Sie den synchronisierten Block (synchronisiert (MyAuthenticator.class) {...}) - Rufen Sie in diesem Block das aktuelle Zugriffs- und Aktualisierungstoken ab - Überprüfen Sie, ob die fehlgeschlagene Anforderung die neueste verwendet hat Zugriffstoken (resp.request (). Header ("Authorization")) - wenn nicht nur noch einmal mit aktualisiertem Zugriffstoken ausführen - andernfalls Aktualisierungstokenfluss ausführen - aktualisierten Zugriff aktualisieren / beibehalten und Aktualisierungstoken aktualisieren - synchronisierten Block beenden - erneut ausführen
Dariusz Wiechecki
65

Wenn Sie Retrofit > = 1.9.0verwenden, können Sie den neuen Interceptor von OkHttp verwenden , der in eingeführt wurde OkHttp 2.2.0. Sie möchten einen Application Interceptor verwenden , mit dem Sie dies tun können retry and make multiple calls.

Ihr Interceptor könnte ungefähr so ​​aussehen wie dieser Pseudocode:

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

}

Nachdem Sie Ihre definiert haben Interceptor, erstellen Sie eine OkHttpClientund fügen Sie den Interceptor als Application Interceptor hinzu .

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

Und schließlich verwenden Sie dies OkHttpClientbeim Erstellen Ihrer RestAdapter.

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

Warnung: Wie Jesse Wilson(von Square) hier erwähnt , ist dies eine gefährliche Menge an Kraft.

Angesichts dessen denke ich definitiv, dass dies der beste Weg ist, so etwas jetzt zu handhaben. Wenn Sie Fragen haben, zögern Sie bitte nicht, in einem Kommentar zu fragen.

theblang
quelle
2
Wie erreichen Sie einen synchronen Anruf in Android, wenn Android keine Netzwerkanrufe im Hauptthread zulässt? Ich habe Probleme beim Zurückgeben einer Antwort von einem asynchronen Aufruf.
lgdroid57
1
@ lgdroid57 Sie sind korrekt, daher sollten Sie sich bereits in einem anderen Thread befinden, als Sie die ursprüngliche Anforderung gestartet haben, die die Ausführung des Interceptors ausgelöst hat.
Theblang
3
Dies funktionierte hervorragend, außer dass ich sicherstellen musste, dass die vorherige Antwort geschlossen wurde, sonst würde ich die vorherige Verbindung verlieren ... final Request newRequest = request.newBuilder () .... build (); response.body (). close (); return chain.proceed (newRequest);
DallinDyer
Vielen Dank! Ich hatte ein Problem, bei dem der Rückruf der ursprünglichen Anforderung die Fehlermeldung "geschlossen" anstelle der ursprünglichen Antwort erhielt, da der Textkörper im Interceptor verwendet wurde. Ich konnte dies für erfolgreiche Antworten beheben, aber ich konnte dies für fehlgeschlagene Antworten nicht beheben. Irgendwelche Vorschläge?
lgdroid57
Danke @mattblang, es sieht gut aus. Eine Frage: Wird der Anforderungsrückruf garantiert auch bei dem erneuten Versuch aufgerufen?
Luca Fagioli
23

TokenAuthenticator hängt von einer Serviceklasse ab. Die Serviceklasse hängt von einer OkHttpClient-Instanz ab. Um einen OkHttpClient zu erstellen, benötige ich den TokenAuthenticator. Wie kann ich diesen Kreislauf durchbrechen? Zwei verschiedene OkHttpClients? Sie werden verschiedene Verbindungspools haben.

Wenn Sie beispielsweise eine Nachrüstung haben TokenService, die Sie in Ihrem AuthenticatorGerät benötigen, aber nur eine einrichten möchten, für die OkHttpClientSie eine TokenServiceHolderals Abhängigkeit verwenden können TokenAuthenticator. Sie müssten einen Verweis darauf auf Anwendungsebene (Singleton) pflegen. Dies ist einfach, wenn Sie Dolch 2 verwenden. Andernfalls erstellen Sie einfach ein Klassenfeld in Ihrer Anwendung.

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

In TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

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

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

Client-Setup:

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

Wenn Sie Dagger 2 oder ein ähnliches Framework für die Abhängigkeitsinjektion verwenden, finden Sie in den Antworten auf diese Frage einige Beispiele

David Rawson
quelle
Wo wird die TokenServiceKlasse erstellt?
Yogesh Suthar
@YogeshSuthar es ist ein Nachrüstdienst - siehe die verwandte Frage
David Rawson
Danke, können Sie die Implementierung von refreshToken()von bereitstellen service.refreshToken().execute();. Ich kann die Implementierung nirgendwo finden.
Yogesh Suthar
@Yogesh Die refreshToken-Methode stammt aus Ihrer API. Was auch immer Sie anrufen, um ein Token zu aktualisieren (ein Anruf mit Benutzername und Passwort vielleicht?). Oder vielleicht eine Anfrage, bei der Sie ein Token einreichen und die Antwort ein neues Token ist
David Rawson
5

Die Verwendung von TokenAuthenticatorlike @ theblang answer ist eine korrekte Methode für den Umgang refresh_token.

Hier ist mein Gerät (ich habe Kotlin, Dagger, RX verwendet, aber Sie können diese Idee für die Implementierung in Ihrem Fall verwenden).
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()
    }
}

Um einen Abhängigkeitszyklus wie @Brais Gabin Kommentar zu verhindern, erstelle ich 2 Schnittstellen wie

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

und

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

AccessTokenWrapper Klasse

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 Klasse

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

Mein Abfangjäger

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

Fügen Sie schließlich Interceptorund Authenticatorzu Ihrem OKHttpClientbeim Erstellen des Dienstes hinzuFügen Sie PotoAuthApi hinzu

Demo

https://github.com/PhanVanLinh/AndroidMVPKotlin

Hinweis

Authenticator Flow
  • Beispiel API gibt getImage()401 Fehlercode zurück
  • authenticateMethode im Inneren TokenAuthenticatorwird ausgelöst
  • Synchronisieren noneAuthAPI.refreshToken(...)aufgerufen
  • Nach der noneAuthAPI.refreshToken(...)Antwort -> wird ein neues Token zum Header hinzugefügt
  • getImage()wird AUTO mit neuem Header aufgerufen (wird diesen Anruf HttpLogging NICHT protokollieren ) ( interceptinnen wird AuthInterceptor NICHT aufgerufen )
  • Wenn getImage()immer noch mit Fehler 401, gescheitert authenticateMethode innerhalb TokenAuthenticatorWillen feuerte wieder und wieder , dann wird es Fehler über Call - Methode viel Zeit werfen ( java.net.ProtocolException: Too many follow-up requests). Sie können dies verhindern, indem Sie die Antwort zählen . Beispiel : Wenn Sie return nullin authenticatenach 3 - mal wiederholt, getImage()wird beenden undreturn response 401

  • Wenn die getImage()Antwort erfolgreich ist =>, erhalten wir das Ergebnis normal (wie Sie es getImage()ohne Fehler aufrufen ).

Hoffe es hilft

Phan Van Linh
quelle
Diese Lösung verwendet zwei verschiedene OkHttpClients, wie in Ihrer ServiceGenerator-Klasse ersichtlich.
SpecialSnowflake
@SpecialSnowflake du hast recht. Wenn Sie meiner Lösung folgen, müssen Sie 2 OkHttp erstellen, da ich 2 Dienste erstellt habe (oauth und none auth). Ich denke, es wird kein Problem verursachen. Lassen Sie mich Ihre Idee wissen
Phan Van Linh
1

Ich weiß, dass dies ein alter Thread ist, aber nur für den Fall, dass jemand darüber stolpert.

TokenAuthenticator hängt von einer Serviceklasse ab. Die Serviceklasse hängt von einer OkHttpClient-Instanz ab. Um einen OkHttpClient zu erstellen, benötige ich den TokenAuthenticator. Wie kann ich diesen Kreislauf durchbrechen? Zwei verschiedene OkHttpClients? Sie werden verschiedene Verbindungspools haben.

Ich hatte das gleiche Problem, aber ich wollte nur einen OkHttpClient erstellen, da ich nicht glaube, dass ich nur für den TokenAuthenticator selbst einen anderen benötige. Ich habe Dagger2 verwendet, sodass ich die Serviceklasse als Lazy in den TokenAuthenticator, können Sie mehr über faule Injektion in Dolch 2 lesen hier , aber es ist wie im Grunde zu Dagger sagen zu nicht gehen und den Service des TokenAuthenticator sofort benötigt erstellen.

In diesem SO-Thread finden Sie Beispielcode: Wie kann eine zirkuläre Abhängigkeit aufgelöst werden, während Dagger2 noch verwendet wird?

Boda
quelle
0

Sie können versuchen, eine Basisklasse für alle Ihre Loader zu erstellen, in der Sie eine bestimmte Ausnahme abfangen und dann nach Bedarf handeln können. Stellen Sie sicher, dass sich alle Ihre verschiedenen Lader von der Basisklasse aus erstrecken, um das Verhalten zu verbreiten.

k3v1n4ud3
quelle
Nachrüstung funktioniert so nicht. Es verwendet Java-Annotationen und Schnittstellen, um einen API-Aufruf zu beschreiben
Daniel Zolnai
Ich weiß, wie Nachrüstungen funktionieren, aber Sie "verpacken" Ihre API-Aufrufe immer noch in eine AsynTask, nicht wahr?
k3v1n4ud3
Nein, ich verwende die Anrufe mit einem Rückruf, sodass sie asynchron ausgeführt werden.
Daniel Zolnai
Dann können Sie wahrscheinlich eine Basis-Rückrufklasse erstellen und alle Ihre Rückrufe dazu bringen, sie zu erweitern.
k3v1n4ud3
2
Irgendeine Lösung dafür? Ist genau mein Fall hier. = /
Hugo Nogueira
0

Nach langer Recherche habe ich den Apache-Client so angepasst, dass er das Aktualisieren von AccessToken für die Nachrüstung übernimmt, in dem Sie das Zugriffstoken als Parameter senden.

Initiieren Sie Ihren Adapter mit Cookie Persistent Client

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

Cookie Persistenter Client, der Cookies für alle Anforderungen verwaltet und bei jeder Anforderungsantwort prüft. Wenn es sich um einen nicht autorisierten Zugriff handelt, ERROR_CODE = 401, aktualisiert das Zugriffstoken und ruft die Anforderung ab, andernfalls verarbeitet er nur die Anforderung.

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
quelle
Gibt es einen Grund, warum Sie den ApacheClient anstelle der vorgeschlagenen Lösung verwenden? Nicht, dass es keine gute Lösung wäre, aber es benötigt viel mehr Codierung als Interceptors.
Daniel Zolnai
Der maßgeschneiderte Client für dauerhafte Cookies verwaltet die Sitzung während der gesamten Dienste. Selbst in Request Intercceptor können Sie in Headern Zugriff hinzufügen. Aber was ist, wenn Sie es als Parameter hinzufügen möchten? Auch OKHTTPClient hat Einschränkungen. ref: stackoverflow.com/questions/24594823/…
Suneel Prakash
Es ist allgemeiner, in jedem Fall verwendet zu werden. 1. Cookie Persistent Client 2. Akzeptiert HTTP- und HTTPS-Anforderungen. 3. Aktualisieren Sie das Zugriffstoken in Params.
Suneel Prakash
0

Die Verwendung eines Interceptors (Injizieren des Tokens) und eines Authenticators (Aktualisierungsvorgänge) erledigt den Job, aber:

Ich hatte auch ein Problem mit einem doppelten Anruf: Der erste Anruf gab immer einen 401 zurück : Das Token wurde beim ersten Anruf (Interceptor) nicht injiziert und der Authentifikator wurde aufgerufen: Es wurden zwei Anforderungen gestellt.

Das Update bestand nur darin, die Anforderung an den Build im Interceptor erneut zu aktivieren:

VOR:

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

NACH DEM:

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

IN EINEM BLOCK:

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

Ich hoffe es hilft.

Bearbeiten: Ich habe keine Möglichkeit gefunden, den ersten Aufruf zu vermeiden, immer 401 nur mit dem Authentifikator und ohne Interceptor zurückzugeben

Sigrun
quelle
-2

Für alle, die gleichzeitige / parallele Anrufe beim Aktualisieren des Tokens lösen möchten. Hier ist eine Problemumgehung

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
quelle