Actualisation du jeton OAuth à l'aide de Retrofit sans modifier tous les appels

157

Nous utilisons Retrofit dans notre application Android pour communiquer avec un serveur sécurisé OAuth2. Tout fonctionne à merveille, nous utilisons RequestInterceptor pour inclure le jeton d'accès à chaque appel. Cependant, il y aura des moments où le jeton d'accès expirera et le jeton doit être actualisé. Lorsque le jeton expire, le prochain appel sera renvoyé avec un code HTTP non autorisé, ce qui est facile à surveiller. Nous pourrions modifier chaque appel Retrofit de la manière suivante: dans le rappel d'échec, vérifiez le code d'erreur, s'il est égal à Non autorisé, actualisez le jeton OAuth, puis répétez l'appel Retrofit. Cependant, pour cela, tous les appels doivent être modifiés, ce qui n'est pas une solution facilement maintenable et bonne. Existe-t-il un moyen de le faire sans modifier tous les appels de retrofit?

Daniel Zolnai
la source
1
Cela semble pertinent pour mon autre question . J'y reviendrai bientôt, mais une approche possible consiste à envelopper OkHttpClient. Quelque chose comme ceci: github.com/pakerfeldt/signpost-retrofit De plus, puisque j'utilise RoboSpice avec Retrofit, la création d'une classe Request de base peut également être une autre approche possible. Vous devrez probablement trouver comment réaliser votre flux sans contexte, peut-être en utilisant Otto / EventBus.
Hassan Ibraheem
1
Eh bien, vous pouvez le fourcher et supprimer les cas inutiles. Je vais examiner cela peut-être aujourd'hui, et poster ici si j'ai réussi à résoudre notre problème.
Daniel Zolnai
2
Il s'est avéré que la bibliothèque ne gérait pas de jetons rafraîchissants, mais m'a donné une idée. J'ai fait un petit résumé de certains! Code non testé, mais en théorie, je pense que cela devrait fonctionner: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai
3
@neworld Une solution à laquelle je peux penser: synchroniser le changeTokenInRequest (...), et à la première ligne, vérifier la date de la dernière actualisation du jeton. S'il y a quelques secondes (millisecondes), n'actualisez pas le jeton. Vous pouvez également définir ce délai sur 1 heure environ, pour cesser de demander constamment de nouveaux jetons lorsqu'il y a un autre problème en dehors du jeton obsolète.
Daniel Zolnai
2
Retrofit 1.9.0 vient d'ajouter la prise en charge d'OkHttp 2.2, qui a des intercepteurs. Cela devrait rendre votre travail beaucoup plus facile. Pour plus d'informations, voir: github.com/square/retrofit/blob/master/… et github.com/square/okhttp/wiki/Interceptors Vous devez également étendre OkHttp pour ces derniers.
Daniel Zolnai

Réponses:

214

Veuillez ne pas utiliser Interceptorspour traiter l'authentification.

Actuellement, la meilleure approche pour gérer l'authentification consiste à utiliser la nouvelle AuthenticatorAPI, conçue spécifiquement à cet effet .

OkHttp demandera automatiquement les Authenticatorinformations d'identification lorsqu'une réponse 401 Not Authorised réessaye la dernière demande ayant échoué avec eux.

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

Attachez un Authenticatorà un OkHttpClientcomme vous le faites avecInterceptors

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

Utilisez ce client lors de la création de votre Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);
lgvalle
la source
6
Cela signifie-t-il que chaque demande échouera toujours une fois ou ajoutez-vous le jeton lorsque vous effectuez des demandes?
Jdruwe
11
@Jdruwe il semble que ce code échoue 1 fois, puis il fera la demande. toutefois, si vous ajoutez un intercepteur dont le seul but est de toujours ajouter un jeton d'accès (qu'il ait expiré ou non), il ne sera appelé que lorsqu'un 401 est reçu, ce qui ne se produira que lorsque ce jeton aura expiré.
narciero
54
TokenAuthenticatordépend d'une serviceclasse. La serviceclasse dépend d'une OkHttpClientinstance. Pour créer un OkHttpClientj'ai besoin du fichier TokenAuthenticator. Comment puis-je briser ce cycle? Deux OkHttpClients différents ? Ils vont avoir différents pools de connexion ...
Brais Gabin
6
Qu'en est-il du nombre de demandes parallèles qui doivent actualiser le jeton? Il y aura plusieurs demandes de jetons d'actualisation en même temps. Comment l'éviter?
Igor Kostenko
10
Ok, donc la solution au problème de @ Ihor pourrait être de synchroniser le code dans Authenticator. Cela a résolu le problème dans mon cas. dans la méthode Request authenticate (...): - faire n'importe quoi d'initalisation - démarrer le bloc synchronisé (synchronized (MyAuthenticator.class) {...}) - dans ce bloc récupérer l'accès actuel et le jeton d'actualisation - vérifier si la demande échouée utilisait le dernier jeton d'accès (resp.request (). header ("Authorization")) - si ce n'est pas simplement l'exécuter à nouveau avec le jeton d'accès mis à jour - sinon exécuter le flux de jeton d'actualisation - mettre à jour / conserver l'accès mis à jour et le jeton d'actualisation - terminer le bloc synchronisé - réexécuter
Dariusz Wiechecki
65

Si vous utilisez Retrofit > =, 1.9.0vous pouvez utiliser le nouvel intercepteur d' OkHttp , qui a été introduit dans OkHttp 2.2.0. Vous souhaitez utiliser un intercepteur d'application , ce qui vous permet de le faire retry and make multiple calls.

Votre intercepteur pourrait ressembler à quelque chose comme ce 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;
    }

}

Après avoir défini votre Interceptor, créez un OkHttpClientet ajoutez l'intercepteur en tant qu'intercepteur d' application .

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

Et enfin, utilisez ceci OkHttpClientlors de la création de votre fichier RestAdapter.

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

Attention: comme Jesse Wilson(de Square) le mentionne ici , il s'agit d'une quantité d'énergie dangereuse.

Cela étant dit, je pense définitivement que c'est la meilleure façon de gérer quelque chose comme ça maintenant. Si vous avez des questions, n'hésitez pas à les poser dans un commentaire.

theblang
la source
2
Comment réalisez-vous un appel synchrone sous Android alors qu'Android n'autorise pas les appels réseau sur le thread principal? Je rencontre des problèmes pour renvoyer une réponse d'un appel asynchrone.
lgdroid57
1
@ lgdroid57 Vous avez raison, vous devriez donc déjà être sur un autre thread lorsque vous avez lancé la requête d'origine qui a déclenché l'exécution de l'intercepteur.
theblang
3
Cela a très bien fonctionné sauf que je devais m'assurer de fermer la réponse précédente ou je perdrais la connexion précédente ... Demande finale newRequest = request.newBuilder () .... build (); response.body (). close (); return chain.proceed (newRequest);
DallinDyer
Merci! Je rencontrais un problème où le rappel de la demande d'origine recevait un message d'erreur «fermé» au lieu de la réponse d'origine, en raison du corps consommé dans l'intercepteur. J'ai pu résoudre ce problème pour les réponses réussies, mais je n'ai pas été en mesure de résoudre ce problème pour les réponses ayant échoué. Aucune suggestion?
lgdroid57
Merci @mattblang, ça a l'air sympa. Une question: le rappel de requête est-il garanti d'être appelé même lors de la nouvelle tentative?
Luca Fagioli
23

TokenAuthenticator dépend d'une classe de service. La classe de service dépend d'une instance OkHttpClient. Pour créer un OkHttpClient, j'ai besoin du TokenAuthenticator. Comment puis-je briser ce cycle? Deux OkHttpClients différents? Ils vont avoir différents pools de connexions.

Si vous avez, par exemple, un Retrofit TokenServicedont vous avez besoin à l'intérieur de votre Authenticatormais que vous ne souhaitez en installer qu'un pour lequel OkHttpClientvous pouvez utiliser a TokenServiceHoldercomme dépendance TokenAuthenticator. Vous devrez en conserver une référence au niveau de l'application (singleton). C'est facile si vous utilisez Dagger 2, sinon créez simplement un champ de classe dans votre application.

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

Dans TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

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

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

Configuration du client:

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

Si vous utilisez Dagger 2 ou un framework d'injection de dépendances similaire, il y a quelques exemples dans les réponses à cette question

David Rawson
la source
Où la TokenServiceclasse est -elle créée?
Yogesh Suthar
@YogeshSuthar c'est un service Retrofit - voir la question associée
David Rawson
Merci, pouvez - vous fournir la mise en œuvre refreshToken()de service.refreshToken().execute();. Impossible de trouver sa mise en œuvre nulle part.
Yogesh Suthar
@Yogesh La méthode refreshToken provient de votre API. Quoi que vous appeliez pour actualiser un token (un appel avec nom d'utilisateur et mot de passe peut-être?) Ou peut-être une demande où vous soumettez un jeton et la réponse est un nouveau jeton
David Rawson
5

Utiliser TokenAuthenticatorune réponse comme @theblang est une manière correcte de gérer refresh_token.

Voici mon outil (j'utilise Kotlin, Dagger, RX mais vous pouvez utiliser cette idée pour implémenter votre cas)
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()
    }
}

Pour éviter le cycle de dépendance comme le commentaire @Brais Gabin, je crée 2 interfaces comme

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

et

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

AccessTokenWrapper classe

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 classe

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

Mon intercepteur

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

Enfin, ajoutez Interceptoret Authenticatorà votre OKHttpClientlors de la création du service PotoAuthApi

Démo

https://github.com/PhanVanLinh/AndroidMVPKotlin

Remarque

Flux d'authentificateur
  • Exemple de getImage()code d'erreur de retour d' API 401
  • authenticatela méthode à l'intérieur TokenAuthenticatorsera tirée
  • Synchroniser noneAuthAPI.refreshToken(...)appelé
  • Après la noneAuthAPI.refreshToken(...)réponse -> un nouveau jeton s'ajoutera à l'en-tête
  • getImage()sera AUTO appelé avec un nouvel en-tête ( HttpLogging ne journalisera pas cet appel) (à l' interceptintérieur AuthInterceptor ne sera pas appelé )
  • Si getImage()toujours échoué avec l'erreur 401, la authenticateméthode à l'intérieur TokenAuthenticatorsera déclenchée à NOUVEAU et à NOUVEAU, puis elle lancera une erreur sur la méthode d'appel plusieurs fois ( java.net.ProtocolException: Too many follow-up requests). Vous pouvez l'empêcher en comptant la réponse . Par exemple, si vous return nullen authenticateaprès 3 fois nouvelle tentative, getImage()va finir etreturn response 401

  • Si getImage()réponse réussie => nous obtiendrons le résultat normalement (comme vous appelez getImage()sans erreur)

J'espère que ça aide

Phan Van Linh
la source
Cette solution utilise 2 OkHttpClients différents, comme en témoigne votre classe ServiceGenerator.
SpecialSnowflake
@SpecialSnowflake vous avez raison. Si vous suivez ma solution, vous devez créer 2 OkHttp car j'ai créé 2 services (oauth et aucun auth). Je pense que cela ne posera aucun problème. Faites-moi savoir votre idée
Phan Van Linh
1

Je sais que c'est un vieux fil, mais juste au cas où quelqu'un y trébucherait.

TokenAuthenticator dépend d'une classe de service. La classe de service dépend d'une instance OkHttpClient. Pour créer un OkHttpClient, j'ai besoin du TokenAuthenticator. Comment puis-je briser ce cycle? Deux OkHttpClients différents? Ils vont avoir différents pools de connexions.

J'étais confronté au même problème, mais je voulais créer un seul OkHttpClient car je ne pense pas que j'en ai besoin d'un autre uniquement pour le TokenAuthenticator lui-même, j'utilisais Dagger2, donc j'ai fini par fournir la classe de service comme Lazy injecté dans le TokenAuthenticator, vous pouvez en savoir plus sur l'injection Lazy dans dagger 2 ici , mais c'est comme dire à Dagger de NE PAS créer le service nécessaire au TokenAuthenticator tout de suite.

Vous pouvez vous référer à ce thread SO pour un exemple de code: Comment résoudre une dépendance circulaire tout en utilisant Dagger2?

Boda
la source
0

Vous pouvez essayer de créer une classe de base pour tous vos chargeurs dans laquelle vous seriez en mesure d'attraper une exception particulière, puis d'agir selon vos besoins. Faites en sorte que tous vos différents chargeurs s'étendent à partir de la classe de base afin de diffuser le comportement.

k3v1n4ud3
la source
La modernisation ne fonctionne pas de cette façon. Il utilise des annotations java et des interfaces pour décrire un appel d'API
Daniel Zolnai
Je sais comment fonctionne la mise à niveau, mais vous «encapsulez» toujours vos appels d'API dans une AsynTask, n'est-ce pas?
k3v1n4ud3
Non, j'utilise les appels avec un rappel, donc ils s'exécutent de manière asynchrone.
Daniel Zolnai
Ensuite, vous pouvez probablement créer une classe de rappel de base et faire en sorte que tous vos rappels l'étendent.
k3v1n4ud3
2
Une solution à cela? Est exactement mon cas ici. = /
Hugo Nogueira
0

Après de longues recherches, j'ai personnalisé le client Apache pour gérer l'actualisation d'AccessToken pour la modernisation dans lequel vous envoyez un jeton d'accès en tant que paramètre.

Initiez votre adaptateur avec un client persistant de cookie

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

Cookie Client persistant qui conserve les cookies pour toutes les demandes et vérifie à chaque réponse de demande, s'il s'agit d'un accès non autorisé ERROR_CODE = 401, actualise le jeton d'accès et rappelle la demande, sinon traite simplement la demande.

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
la source
Y a-t-il une raison pour laquelle vous utilisez ApacheClient au lieu de la solution suggérée? Non pas que ce ne soit pas une bonne solution, mais cela nécessite beaucoup plus de codage que l'utilisation d'intercepteurs.
Daniel Zolnai
Il est personnalisé pour être un client persistant de cookie, maintient la session tout au long des services. Même dans Request Intercceptor, vous pouvez ajouter un jeton d'accès dans les en-têtes. Mais que faire si vous souhaitez l'ajouter en tant que paramètre? OKHTTPClient a également des limitations. ref: stackoverflow.com/questions/24594823/…
Suneel Prakash
Il est plus généralisé pour être utilisé dans tous les cas 1. Client persistant de cookie 2. Accepte les requêtes HTTP et HTTPS 3. Mettre à jour le jeton d'accès dans les paramètres.
Suneel Prakash
0

L'utilisation d'un intercepteur (injecter le jeton) et d'un authentificateur (opérations d'actualisation) fait le travail mais:

J'avais aussi un problème de double appel: le premier appel retournait toujours un 401 : le token n'était pas injecté au premier appel (intercepteur) et l'authentificateur était appelé: deux requêtes étaient faites.

Le correctif consistait simplement à réaffecter la demande à la construction dans l'Interceptor:

AVANT:

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

APRÈS:

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

EN UN BLOC:

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

J'espère que ça aide.

Edit: Je n'ai pas trouvé de moyen d'éviter le premier appel de toujours renvoyer 401 en utilisant uniquement l'authentificateur et aucun intercepteur

Sigrun
la source
-2

À tous ceux qui voulaient résoudre des appels simultanés / parallèles lors de l'actualisation du jeton. Voici une solution de contournement

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
la source