すべての呼び出しを変更せずにRetrofitを使用してOAuthトークンを更新する


157

AndroidアプリでRetrofitを使用して、OAuth2で保護されたサーバーと通信します。すべてが適切に機能します。RequestInterceptorを使用して、各呼び出しにアクセストークンを含めます。ただし、アクセストークンが期限切れになり、トークンを更新する必要がある場合があります。トークンの有効期限が切れると、次の呼び出しは無許可のHTTPコードで返されるため、監視は簡単です。各Retrofit呼び出しを次のように変更できます。失敗コールバックでエラーコードを確認し、Unauthorizedに等しい場合は、OAuthトークンを更新してから、Retrofit呼び出しを繰り返します。ただし、これを行うには、すべての呼び出しを変更する必要があります。これは容易に保守できず、適切な解決策ではありません。すべてのレトロフィット呼び出しを変更せずにこれを行う方法はありますか?


1
これは私の他の質問に関連しているように見えます。すぐにもう一度調べますが、1つの可能なアプローチはOkHttpClientをラップすることです。次のようなもの:github.com/pakerfeldt/signpost-retrofit また、私はRoboSpiceをRetrofitで使用しているため、ベースのRequestクラスを作成する方法も考えられます。おそらく、おそらくOtto / EventBusを使用して、コンテキストなしでフローを実現する方法を理解する必要があります。
ハッサンイブラヒーム14年

1
さて、それをフォークして、不要なケースを削除することができます。今日はたぶんこれを調べて、問題を解決する可能性のある何かを達成したらここに投稿します。
Daniel Zolnai 2014年

2
ライブラリは更新トークンを処理しなかったことがわかりましたが、私にアイ​​デアを与えました。私はいくつかの!unestedコードについて小さな要点を作りましたが、理論的には、それはうまくいくと思います:gist.github.com/ZolnaiDani/9710849
Daniel Zolnai 2014年

3
@neworld考えられる解決策:changeTokenInRequest(...)を同期させ、最初の行で、トークンが最後に更新されたのがいつかを確認します。数秒(ミリ秒)前の場合は、トークンを更新しないでください。また、この時間枠を1時間程度に設定して、古くなっているトークンの外側に別の問題がある場合に、新しいトークンのリクエストを絶えず停止することもできます。
Daniel Zolnai 2014年

2
Retrofit 1.9.0は​​、インターセプターを持つOkHttp 2.2のサポートを追加しました。これはあなたの仕事をずっと簡単にするでしょう。詳細については、github.com / square / retrofit / blob / master / およびgithub.com/square/okhttp/wiki/Interceptorsを参照してください。ただし、これらについてもOkHttpを拡張する必要があります。
Daniel Zolnai、2015年

回答:


213

Interceptors認証に使用しないでください。

現在、認証を処理するための最良のアプローチはAuthenticatorこの目的のために特別に設計された新しいAPI を使用することです。

OkHttpはなり自動的に尋ねるAuthenticator応答がされたときに資格情報の401 Not Authorised 最後の失敗した要求を再試行彼らとを。

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

取り付けAuthenticatorOkHttpClientあなたと同じ方法Interceptors

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

作成時にこのクライアントを使用します Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);

6
これは、すべてのリクエストが常に1回失敗することを意味しますか、それともリクエストを実行するときにトークンを追加しますか?
Jdruwe

11
@Jdruweこのコードは1回失敗するように見え、その後リクエストを行います。ただし、インターセプターを追加する場合、その目的は常にアクセストークンを追加することです(期限が切れているかどうかに関係なく)、これは、トークンが期限切れになったときにのみ発生する401を受信したときにのみ呼び出されます。
narciero

54
TokenAuthenticatorserviceクラスに依存します。serviceクラスが依存するOkHttpClientインスタンス。を作成するOkHttpClientには、が必要TokenAuthenticatorです。どうすればこのサイクルを壊すことができますか?2つの異なるOkHttpClients?彼らは異なる接続プールを使用する予定です...
Brais Gabin

6
トークンを更新する必要がある多くの並列リクエストはどうですか?同時に多くの更新トークン要求になります。それを避ける方法は?
Igor Kostenko

10
OK、@ Ihorの問題の解決策は、Authenticator内のコードを同期させることです。私の場合は問題を解決しました。リクエストauthenticate(...)メソッドで:-初期化を行います-同期ブロックを開始します(synchronized(MyAuthenticator.class){...})-そのブロックで現在のアクセスと更新トークンを取得します-失敗したリクエストが最新のものを使用しているかどうかを確認しますアクセストークン(resp.request()。header( "Authorization"))-更新されたアクセストークンでもう一度実行しない場合-更新トークンフローを実行-更新されたアクセスと更新トークンを更新/永続化-同期ブロックを終了-再実行
ダリウス・ヴィーチェキ

65

使用している場合は改修を > = 1.9.0あなたが使用作ることができるOkHttpの新しいインターセプターに導入されました、OkHttp 2.2.0Application Interceptorを使用すると、次のことが可能になりますretry and make multiple calls

インターセプターは次の疑似コードのようになります。

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

}

を定義したらInterceptor、を作成OkHttpClientし、アプリケーションインターセプターとしてインターセプターを追加します。

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

最後に、をOkHttpClient作成するときにこれを使用しますRestAdapter

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

警告:としてJesse Wilson(広場からは)言及し、ここで、これは、電力の危険な量です。

そうは言っても、これがこのようなものを処理するための最良の方法だと私は間違いなく思います。ご不明な点がございましたら、コメントでお気軽にお問い合わせください。


2
Androidがメインスレッドでのネットワーク呼び出しを許可しない場合、Androidで同期呼び出しをどのように実現しますか?非同期呼び出しから応答を返すときに問題が発生しています。
lgdroid57 2015年

1
@ lgdroid57正解です。したがって、インターセプターの実行をトリガーした元のリクエストを開始したときには、すでに別のスレッドにいるはずです。
theblang 2015年

3
これは、前の応答を確実に閉じる必要があるか、前の接続をリークすることを除いて、うまくいきました... final Request newRequest = request.newBuilder().... build(); response.body()。close(); return chain.proceed(newRequest);
DallinDyer 2015

ありがとう!インターセプターで本体が消費されるため、元の要求のコールバックが元の応答ではなく「クローズ」のエラーメッセージを受信するという問題に遭遇していました。成功した応答ではこれを修正できましたが、失敗した応答ではこれを修正できませんでした。助言がありますか?
lgdroid57 2015

@mattblangに感謝します。1つの質問:要求コールバックは、再試行時でも呼び出されることが保証されていますか?
Luca Fagioli、2015

23

TokenAuthenticatorはサービスクラスに依存します。サービスクラスはOkHttpClientインスタンスに依存します。OkHttpClientを作成するには、TokenAuthenticatorが必要です。どうすればこのサイクルを壊すことができますか?2つの異なるOkHttpClients?それらは異なる接続プールを持つことになります。

たとえば、TokenService内部に必要なレトロフィットAuthenticatorがあっOkHttpClientTokenServiceHolderも、の依存関係として使用できるものだけを設定したい場合TokenAuthenticator。アプリケーション(シングルトン)レベルでの参照を維持する必要があります。Dagger 2を使用している場合、これは簡単です。それ以外の場合は、アプリケーション内にクラスフィールドを作成するだけです。

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

TokenServiceHolder.java

public class TokenServiceHolder {

    TokenService tokenService = null;

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

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

クライアントのセットアップ:

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

Dagger 2または同様の依存関係注入フレームワークを使用している場合、この質問への回答にいくつかの例があります


TokenServiceクラスはどこに作成されますか?
Yogesh Suthar

@YogeshSutharは改造サービスです- 関連する質問を
David Rawson

ありがとう、refreshToken()from の実装を提供できますかservice.refreshToken().execute();。それはどこでも実装を見つけることができません。
Yogesh Suthar

@Yogesh refreshTokenメソッドは、APIからのものです。トークンを更新するために呼び出すものは何でも(ユーザー名とパスワードを使用した呼び出しですか?)。または、トークンを送信し、応答が新しいトークンであるリクエスト
David Rawson

5

TokenAuthenticator@theblangのような答えを使用することは、ハンドルの正しい方法ですrefresh_token

これが私の実装です(私はKotlin、Dagger、RXを使用していますが、このアイデアを使用してケースに実装できます)
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()
    }
}

@Brais Gabinコメントのような依存サイクルを防ぐために、次のような2つのインターフェースを作成します

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

そして

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

AccessTokenWrapper クラス

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 クラス

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

私のインターセプター

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

最後に、追加InterceptorしてAuthenticator、あなたのOKHttpClient作成サービスPotoAuthApi

デモ

https://github.com/PhanVanLinh/AndroidMVPKotlin

注意

オーセンティケーターのフロー
  • サンプルAPI getImage()が401エラーコードを返す
  • authenticate内部のメソッドTokenAuthenticatorが発生します
  • 同期をnoneAuthAPI.refreshToken(...)呼び出す
  • noneAuthAPI.refreshToken(...)応答後->新しいトークンがヘッダーに追加されます
  • getImage()意志AUTOと呼ばれる(新しいヘッダーでHttpLogging ログインしないものとします(この呼び出しを)interceptAuthInterceptor WILLは呼び出されません
  • getImage()それでもエラー401で失敗する場合は、authenticate内部のメソッドがAGAINおよびAGAINTokenAuthenticator起動し、メソッドの呼び出しに関するエラーを何度もスローします(java.net.ProtocolException: Too many follow-up requests)。カウントレスポンスで防ぐことができます。たとえば、もしあなたreturn nullauthenticate3回再試行した後、getImage()なり仕上げreturn response 401

  • getImage()応答が成功した場合=>通常の結果になります(getImage()エラーなしで呼び出すようなもの)

それが役に立てば幸い


このソリューションは、ServiceGeneratorクラスで明らかなように、2つの異なるOkHttpClientsを使用します。
SpecialSnowflake 2018

@SpecialSnowflakeあなたは正しい。私のソリューションに従う場合、2つのサービス(oauthとnone auth)を作成したため、2つのOkHttpを作成する必要があります。何の問題もないと思います。あなたのアイデアを教えてください
ファンヴァンリン2018

1

私はこれが古いスレッドであることを知っていますが、誰かがそれにつまずいた場合に備えて。

TokenAuthenticatorはサービスクラスに依存します。サービスクラスはOkHttpClientインスタンスに依存します。OkHttpClientを作成するには、TokenAuthenticatorが必要です。どうすればこのサイクルを壊すことができますか?2つの異なるOkHttpClients?それらは異なる接続プールを持つことになります。

私は同じ問題に直面していましたが、TokenAuthenticator自体のために別のOkHttpClientが必要だとは思わないため、1つだけOkHttpClientを作成したかったので、Dagger2を使用していたため、LazyがTokenAuthenticatorについては、ダガー2 でのレイジーインジェクションの詳細についてはこちらをご覧ください。ただし、基本的にDaggerに対して、TokenAuthenticatorが必要とするサービスをすぐに作成しないように指示するようなものです。

サンプルコードについては、このSOスレッドを参照できます。Dagger2を使用しながら循環依存関係を解決する方法は?


0

すべてのローダーの基本クラスを作成して、特定の例外をキャッチし、必要に応じて機能させることができます。振る舞いを広げるために、すべての異なるローダーを基本クラスから拡張させます。


後付けはそのようには機能しません。Javaアノテーションとインターフェースを使用してAPI呼び出しを記述します
Daniel Zolnai

改造の仕組みは知っていますが、API呼び出しをAsynTask内に「ラップ」しているのではないでしょうか。
k3v1n4ud3 2014年

いいえ、コールバックで呼び出しを使用しているため、非同期で実行されます。
Daniel Zolnai 2014年

次に、おそらく基本のコールバッククラスを作成し、すべてのコールバックにそれを拡張させることができます。
k3v1n4ud3 2014年

2
これに対する解決策はありますか?ここはまさに私のケースです。= /
Hugo Nogueira、

0

長期にわたる調査の後、Apacheクライアントをカスタマイズして、アクセストークンをパラメーターとして送信するRetrofitの更新AccessTokenを処理しました。

アダプターをCookie Persistent Clientで開始する

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

すべての要求のCookieを維持し、不正なアクセスである場合は各要求の応答を確認するCookie PersistentクライアントERROR_CODE = 401、アクセストークンを更新して要求を取り消す。

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

提案されたソリューションの代わりにApacheClientを使用する理由はありますか?これは良い解決策ではありませんが、Interceptorsを使用する場合と比較して、より多くのコーディングが必要です。
Daniel Zolnai、2015

Cookie永続クライアントになるようにカスタマイズされ、サービス全体でセッションを維持します。Request Intercceptorでも、ヘッダーにaccesstokenを追加できます。しかし、それをパラメーターとして追加したい場合はどうでしょうか?また、OKHTTPClientには制限があります。REF:stackoverflow.com/questions/24594823/...
Suneel Prakashの

これは、どのような場合でも使用できるように一般化されています。1。Cookie Persistent Client 2. HTTPおよびHTTPSリクエストを受け入れます。
Suneel Prakash 2015

0

1つのインターセプター(トークンの挿入)と1つのオーセンティケーター(更新操作)を使用すると、次のことができます。

私も二重呼び出しの問題がありました:最初の呼び出しは常に401を返しました:トークンは最初の呼び出し(インターセプター)で注入されず、オーセンティケーターが呼び出されました:2つの要求が行われました。

修正は、Interceptorのビルドへのリクエストに影響を与えるだけでした。

前:

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

後:

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

1つのブロックで:

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

それが役に立てば幸い。

編集:私は常に認証機能のみを使用してインターセプターを使用せずに401を返す最初の呼び出しを回避する方法を見つけませんでした


-2

トークンを更新するときに同時/並列呼び出しを解決したい人に。これが回避策です

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
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.