모든 통화를 수정하지 않고 Retrofit을 사용하여 OAuth 토큰 새로 고침


157

우리는 OAuth2 보안 서버와 통신하기 위해 Android 앱에서 Retrofit을 사용하고 있습니다. 모든 것이 잘 작동합니다. RequestInterceptor를 사용하여 각 호출마다 액세스 토큰을 포함시킵니다. 그러나 액세스 토큰이 만료되어 토큰을 새로 고쳐야 할 때가 있습니다. 토큰이 만료되면 다음 호출이 인증되지 않은 HTTP 코드와 함께 반환되므로 쉽게 모니터링 할 수 있습니다. 각 Retrofit 호출을 다음과 같은 방식으로 수정할 수 있습니다. 실패 콜백에서 오류 코드가 Unauthorized와 같으면 OAuth 토큰을 새로 고친 다음 Retrofit 호출을 반복하십시오. 그러나이를 위해서는 모든 통화를 수정해야합니다. 이는 유지 관리가 용이하지 않고 좋은 해결책이 아닙니다. 모든 Retrofit 호출을 수정하지 않고이를 수행 할 수있는 방법이 있습니까?


1
이것은 내 다른 질문 과 관련이 있습니다. 곧 다시 살펴볼 것이지만 한 가지 가능한 접근법은 OkHttpClient를 래핑하는 것입니다. 이와 같은 것 : github.com/pakerfeldt/signpost-retrofit 또한, Retrofit과 함께 RoboSpice를 사용하고 있기 때문에 기본 Request 클래스를 생성하는 것도 또 다른 방법 일 수 있습니다. 아마도 오토 / 이벤트 버스를 사용하여 컨텍스트없이 흐름을 달성하는 방법을 알아 내야 할 것입니다.
Hassan Ibraheem

1
글쎄, 당신은 그것을 포크하고 불필요한 경우를 제거 할 수 있습니다. 오늘이 문제를 살펴보고 문제를 해결할 수있는 것을 얻은 경우 여기에 게시하십시오.
Daniel Zolnai

2
라이브러리가 새로 고침 토큰을 처리하지 않았지만 아이디어를 얻었습니다. 나는! estested
Daniel Zolnai

3
@neworld 내가 생각할 수있는 해결책 : changeTokenInRequest (...)를 동기화하고 첫 번째 줄에서 마지막으로 토큰을 새로 고친 시간을 확인하십시오. 몇 초 (밀리 초) 전에 토큰을 새로 고치지 마십시오. 토큰 이외의 다른 문제가 오래되었을 때 새 토큰을 계속 요청하지 않도록이 시간을 1 시간 정도로 설정할 수도 있습니다.
Daniel Zolnai 2016 년

2
Retrofit 1.9.0은 인터셉터가있는 OkHttp 2.2에 대한 지원을 추가했습니다. 이렇게하면 작업이 훨씬 쉬워집니다. 자세한 내용은 다음을 참조하십시오 : github.com/square/retrofit/blob/master/…github.com/square/okhttp/wiki/Interceptors 그래도 OkHttp 를 확장해야합니다.
Daniel Zolnai 2016 년

답변:


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
이것은 모든 요청이 항상 한 번 실패하거나 요청을 할 때 토큰을 추가한다는 것을 의미합니까?
Jdruwe

11
@Jdruwe이 코드는 한 번 실패한 것처럼 보이며 요청을합니다. 그러나 인터셉터를 추가하는 것이 유일한 목적은 항상 액세스 토큰을 추가하는 것입니다 (만료 여부에 관계없이). 토큰이 만료되었을 때만 발생하는 401이 수신 될 때만 호출됩니다.
narciero

54
TokenAuthenticatorservice수업에 따라 다릅니다 . service클래스는에 따라 OkHttpClient인스턴스입니다. 를 만들 OkHttpClient려면가 필요합니다 TokenAuthenticator. 이주기를 어떻게 깨뜨릴 수 있습니까? 두 개의 다른 OkHttpClient? 그들은 다른 연결 수영장을 가질 예정입니다 ...
Brais Gabin

6
토큰을 새로 고쳐야하는 많은 병렬 요청은 어떻습니까? 동시에 많은 새로 고침 토큰 요청이 될 것입니다. 그것을 피하는 방법?
Igor Kostenko

10
@Ihor의 문제에 대한 해결책은 Authenticator 내에서 코드를 동기화하는 것일 수 있습니다. 내 경우에는 문제가 해결되었습니다. request verify (...) 메소드에서 :-초기화를 수행합니다.-동기화 된 블록 시작 (synchronized (MyAuthenticator.class) {...})-해당 블록에서 현재 액세스 검색 및 토큰 새로 고침-실패한 요청이 최신을 사용 중인지 확인 액세스 토큰 (resp.request (). header ( "Authorization"))-업데이트 된 액세스 토큰으로 다시 실행하지 않으면 새로 고침 토큰 흐름 실행-업데이트 된 액세스 및 업데이트 토큰 유지-동기화 된 블록 완료-다시 실행
다리우스 Wiechecki

65

당신이 사용하는 경우 개조를 > = 1.9.0당신은의 사용을 만들 수 OkHttp의 새로운 인터셉터 에 도입되었다 OkHttp 2.2.0. Application 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 (Square에서) 여기 에서 언급 했듯이 이것은 위험한 양의 힘입니다.

그렇게 말하면서, 나는 이것이 이것이 지금 이와 같은 것을 처리하는 가장 좋은 방법이라고 생각합니다. 궁금한 점이 있으면 언제든지 문의하십시오.


2
Android가 메인 스레드에서 네트워크 호출을 허용하지 않는 경우 Android에서 동기 호출을 어떻게 수행합니까? 비동기 호출에서 응답을 반환하는 데 문제가 있습니다.
lgdroid57

1
@ lgdroid57 맞습니다. 따라서 인터셉터 실행을 트리거 한 원래 요청을 시작할 때 이미 다른 스레드에 있어야합니다.
theblang 2016 년

3
이전 응답을 닫아야하거나 이전 연결을 유출해야한다는 점을 제외하고는 훌륭하게 작동했습니다 ... final Request newRequest = request.newBuilder () .... build (); response.body (). close (); return chain.proceed (newRequest);
DallinDyer

감사! 인터셉터에서 본문이 소비되어 원래 요청의 콜백에서 원래 응답 대신 "닫힘"이라는 오류 메시지가 표시되는 문제가 발생했습니다. 성공적인 응답으로이 문제를 해결할 수 있었지만 실패한 응답으로이 문제를 해결할 수 없었습니다. 어떤 제안?
lgdroid57

@ mattblang에게 감사드립니다. 한 가지 질문 : 요청 콜백이 재 시도에서도 호출되도록 보장됩니까?
Luca Fagioli

23

TokenAuthenticator는 서비스 클래스에 따라 다릅니다. 서비스 클래스는 OkHttpClient 인스턴스에 따라 다릅니다. OkHttpClient를 만들려면 TokenAuthenticator가 필요합니다. 이주기를 어떻게 끊을 수 있습니까? 두 개의 다른 OkHttpClient? 그들은 다른 연결 풀을 가질 것입니다.

예를 들어, TokenService내부에 필요한 개조 가 Authenticator있지만 하나만 설정하려는 OkHttpClient경우 TokenServiceHolder에 대한 종속성으로 사용할 수 있습니다 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())
    }
}

마지막으로 서비스 PotoAuthApi를 만들 때 Interceptorand Authenticator에 추가 하십시오.OKHttpClient

데모

https://github.com/PhanVanLinh/AndroidMVPKotlin

노트

인증 자 흐름
  • API getImage()리턴 401 오류 코드 예
  • authenticate방법의 내부는 TokenAuthenticator것이다 발사
  • noneAuthAPI.refreshToken(...)호출 된 동기화
  • noneAuthAPI.refreshToken(...)응답 후 -> 새 토큰이 헤더에 추가됩니다.
  • getImage()의지 AUTO라는 (새 헤더로 HttpLogging 로그인하지 않을 것이다 (이 호출)를 intercept내부에 AuthInterceptor 의지가 호출되지 않습니다 )
  • getImage()오류 401로 여전히 실패 하면 authenticate내부 메소드 TokenAuthenticatorAGAIN 및 AGAIN발생시키고 호출 메소드에 대한 오류를 여러 번 발생시킵니다 ( java.net.ProtocolException: Too many follow-up requests). 카운트 응답으로 방지 할 수 있습니다 . 예를 들어, 만약 당신이 return null에서 authenticate3 번 시도 후 getImage()것이다 완료return response 401

  • 경우 getImage()(당신이 전화 같은 응답 성공하기 => 우리는 일반적으로 결과를 발생합니다 getImage()오류없이)

도움이되기를 바랍니다.


이 솔루션은 ServiceGenerator 클래스에서 알 수 있듯이 2 개의 다른 OkHttpClient를 사용합니다.
SpecialSnowflake

@SpecialSnowflake 당신이 맞아요. 내 솔루션을 따르는 경우 2 개의 서비스 (oauth 및 none auth)를 만들었으므로 2 OkHttp를 만들어야합니다. 나는 그것이 아무런 문제를 일으키지 않을 것이라고 생각합니다. 나를 당신의 생각을 알려주세요
판 반 린

1

나는 이것이 오래된 실을 알고 있지만 누군가가 넘어 질 경우를 대비하여.

TokenAuthenticator는 서비스 클래스에 따라 다릅니다. 서비스 클래스는 OkHttpClient 인스턴스에 따라 다릅니다. OkHttpClient를 만들려면 TokenAuthenticator가 필요합니다. 이주기를 어떻게 끊을 수 있습니까? 두 개의 다른 OkHttpClient? 그들은 다른 연결 풀을 가질 것입니다.

나는 같은 문제에 직면했지만 TokAuthenticator 자체를 위해 다른 하나가 필요하다고 생각하지 않고 OkHttpClient becuase를 하나만 만들고 싶었습니다 .Dagger2를 사용하고 있었기 때문에 Lazy가 주입 된 서비스 클래스를 제공 했습니다. TokenAuthenticator, 단검 2의 Lazy injection에 대한 자세한 내용은 여기를 참조하십시오 . 그러나 기본적으로 Dagger가 TokenAuthenticator에 필요한 서비스를 작성 하지 말라고 말하는 것과 같습니다 .

샘플 코드는이 SO 스레드를 참조 할 수 있습니다. Dagger2를 계속 사용하면서 순환 종속성을 해결하는 방법은 무엇입니까?


0

특정 예외를 포착 한 다음 필요에 따라 작동 할 수있는 모든 로더에 대한 기본 클래스를 작성할 수 있습니다. 행동을 넓히려면 다른 모든 로더를 기본 클래스에서 확장하십시오.


개조는 그런 식으로 작동하지 않습니다. Java 주석과 인터페이스를 사용하여 API 호출을 설명합니다.
Daniel Zolnai

개조가 어떻게 작동하는지 알고 있지만 여전히 AsynTask 내부에서 API 호출을 "래핑"하고 있습니까?
k3v1n4ud3

아니요, 콜백과 함께 호출을 사용하므로 비동기식으로 실행됩니다.
Daniel Zolnai 2016 년

그런 다음 기본 콜백 클래스를 만들고 모든 콜백을 확장 할 수 있습니다.
k3v1n4ud3

2
이것에 대한 해결책? 정확히 내 경우입니다. = /
Hugo Nogueira

0

오랜 연구 끝에, 액세스 토큰을 매개 변수로 전송하는 RefreshTo Access Forken for Retrofit을 처리하도록 Apache 클라이언트를 사용자 정의했습니다.

쿠키 영구 클라이언트를 사용하여 어댑터 시작

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

쿠키 영구 클라이언트는 모든 요청에 ​​대해 쿠키를 유지 관리하고 각 요청 응답을 확인합니다 (허가되지 않은 액세스 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를 사용하는 이유가 있습니까? 좋은 해결책은 아니지만 인터셉터를 사용하는 것보다 훨씬 많은 코딩이 필요합니다.
Daniel Zolnai

쿠키 영구 클라이언트로 사용자 정의되고 서비스 전체에서 세션을 유지합니다. 요청 인터셉터에서도 헤더에 액세스 토큰을 추가 할 수 있습니다. 그러나 매개 변수로 추가하려면 어떻게해야합니까? 또한 OKHTTPClient에는 제한이 있습니다. 심판 : stackoverflow.com/questions/24594823/…
Suneel Prakash

모든 경우에 사용되는 것이보다 일반화되었습니다. 1. 쿠키 영구 클라이언트 2. HTTP 및 HTTPS 요청을 수락합니다. 3. 액세스 토큰을 매개 변수로 업데이트합니다.
Suneel Prakash

0

하나의 인터셉터 (토큰 주입)와 하나의 인증 자 (새로 고치기 조작)를 사용하면 다음과 같은 작업을 수행합니다.

이중 호출 문제도있었습니다. 첫 번째 호출은 항상 401을 반환 했습니다. 첫 번째 호출 (인터셉터)에서 토큰이 주입되지 않고 인증자가 호출되었습니다. 두 가지 요청이 이루어졌습니다.

이 수정은 인터셉터의 빌드 요청에 영향을 미쳤습니다.

전에:

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

한 블록으로 :

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.