Làm mới mã thông báo OAuth bằng Retrofit mà không sửa đổi tất cả các cuộc gọi


157

Chúng tôi đang sử dụng Retrofit trong ứng dụng Android của mình, để liên lạc với máy chủ được bảo mật OAuth2. Mọi thứ đều hoạt động tốt, chúng tôi sử dụng RequestInterceptor để bao gồm mã thông báo truy cập với mỗi cuộc gọi. Tuy nhiên, sẽ có lúc, mã thông báo truy cập sẽ hết hạn và mã thông báo cần được làm mới. Khi mã thông báo hết hạn, cuộc gọi tiếp theo sẽ trở lại với mã HTTP trái phép, do đó rất dễ theo dõi. Chúng tôi có thể sửa đổi mỗi cuộc gọi Retrofit theo cách sau: Trong cuộc gọi lại thất bại, hãy kiểm tra mã lỗi, nếu nó bằng Un trái phép, hãy làm mới mã thông báo OAuth, sau đó lặp lại cuộc gọi Retrofit. Tuy nhiên, đối với điều này, tất cả các cuộc gọi nên được sửa đổi, đây không phải là một giải pháp dễ bảo trì và tốt. Có cách nào để làm điều này mà không sửa đổi tất cả các cuộc gọi Retrofit không?


1
Điều này có vẻ liên quan đến câu hỏi khác của tôi . Tôi sẽ xem xét lại sớm, nhưng một cách tiếp cận khả thi là gói OkHttpClient. Một cái gì đó như thế này: github.com/pakerfeldt/signpost-retrofit Ngoài ra, vì tôi đang sử dụng RoboSpice với Retrofit, tạo một lớp Yêu cầu cơ sở cũng có thể là một cách tiếp cận khả thi khác. Có lẽ bạn sẽ phải tìm ra cách để đạt được lưu lượng của mình mà không cần Ngữ cảnh, có thể sử dụng Otto / EventBus.
Hassan Ibraheem

1
Vâng, bạn có thể rẽ nhánh nó, và loại bỏ các trường hợp không cần thiết. Tôi sẽ xem xét điều này có thể ngày hôm nay, và đăng ở đây nếu tôi đạt được điều gì đó có thể giải quyết vấn đề của chúng tôi.
Daniel Zolnai

2
Hóa ra, thư viện đã không xử lý các mã thông báo mới, nhưng đã cho tôi một ý tưởng. Tôi đã thực hiện một ý chính nhỏ về một số mã chưa được kiểm tra, nhưng về lý thuyết, tôi nghĩ rằng nó nên hoạt động: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai 22/03/2016

3
@neworld Một giải pháp tôi có thể nghĩ đến: làm cho đồng bộ hóa ChangeTokenInRequest (...) và ở dòng đầu tiên, kiểm tra xem lần cuối mã thông báo được làm mới lần cuối là khi nào. Nếu chỉ vài giây (mili giây) trước đây, đừng làm mới mã thông báo. Bạn cũng có thể đặt khung thời gian này thành 1 giờ hoặc lâu hơn, để ngừng liên tục yêu cầu mã thông báo mới khi có một vấn đề khác bên ngoài mã thông báo bị lỗi thời.
Daniel Zolnai

2
Retrofit 1.9.0 chỉ cần thêm hỗ trợ cho OkHttp 2.2, có phần chặn. Điều này sẽ làm cho công việc của bạn dễ dàng hơn rất nhiều. Để biết thêm thông tin, hãy xem: github.com/sapes/retrofit/blob/master/ mẹogithub.com/sapes/okhttp/wiki/Interceptors Bạn cũng phải mở rộng OkHttp cho những điều này.
Daniel Zolnai

Câu trả lời:


213

Xin vui lòng không sử dụng Interceptorsđể đối phó với xác thực.

Hiện tại, cách tiếp cận tốt nhất để xử lý xác thực là sử dụng AuthenticatorAPI mới , được thiết kế riêng cho mục đích này .

OkHttp sẽ tự động hỏi sự Authenticatorcho thông tin khi một phản ứng được 401 Not Authorised thử lại yêu cầu không thành cuối cùng với họ.

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

Đính kèm một Authenticatorđến một OkHttpClientgiống như cách bạn làm vớiInterceptors

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

Sử dụng ứng dụng khách này khi tạo Retrofit RestAdapter

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

6
Điều này có nghĩa là mọi yêu cầu sẽ luôn thất bại 1 lần hoặc bạn có thêm mã thông báo khi thực hiện yêu cầu không?
Jdruwe

11
@Jdruwe Có vẻ như mã này sẽ thất bại 1 lần, và sau đó nó sẽ thực hiện yêu cầu. tuy nhiên nếu bạn thêm một thiết bị chặn mà mục đích duy nhất là luôn luôn thêm mã thông báo truy cập (bất kể nó đã hết hạn hay chưa) thì điều này sẽ chỉ được gọi khi nhận được 401, điều này sẽ chỉ xảy ra khi mã thông báo đó đã hết hạn.
narciero

54
TokenAuthenticatorphụ thuộc vào một servicelớp Các servicelớp học phụ thuộc vào một OkHttpClientví dụ. Để tạo một OkHttpClienttôi cần TokenAuthenticator. Làm thế nào tôi có thể phá vỡ chu kỳ này? Hai OkHttpClients khác nhau ? Họ sẽ có các nhóm kết nối khác nhau ...
Brais Gabin

6
Làm thế nào về nhiều yêu cầu song song cần làm mới mã thông báo? Nó sẽ được nhiều yêu cầu mã thông báo làm mới cùng một lúc. Làm thế nào để tránh nó?
Igor Kostenko

10
Ok, vậy giải pháp cho vấn đề của @ Ihor có thể là đồng bộ hóa mã bên trong Authenticator. Nó giải quyết vấn đề trong trường hợp của tôi. trong phương thức xác thực yêu cầu (...): - thực hiện bất kỳ nội dung kích hoạt nào - bắt đầu khối được đồng bộ hóa (được đồng bộ hóa (MyAuthenticator. class) {...}) - trong khối đó truy xuất mã thông báo truy cập và làm mới hiện tại - kiểm tra xem yêu cầu thất bại có đang sử dụng mới nhất không mã thông báo truy cập (resp.request (). tiêu đề ("Ủy quyền")) - nếu không chỉ chạy lại một lần nữa với mã thông báo truy cập được cập nhật - nếu không thì chạy luồng làm mới mã thông báo - cập nhật / duy trì truy cập cập nhật và làm mới mã thông báo - kết thúc khối được đồng bộ hóa - chạy lại
Dariusz Wiechecki

65

Nếu bạn đang sử dụng Retrofit > = 1.9.0thì bạn có thể sử dụng Thiết bị đánh chặn mới của OkHttp , được giới thiệu trong OkHttp 2.2.0. Bạn sẽ muốn sử dụng một Thiết bị đánh chặn ứng dụng , cho phép bạn retry and make multiple calls.

Thiết bị đánh chặn của bạn có thể trông giống như mã giả này:

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

}

Sau khi bạn xác định Interceptor, hãy tạo OkHttpClientvà thêm bộ chặn dưới dạng Bộ chặn ứng dụng .

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

Và cuối cùng, sử dụng điều này OkHttpClientkhi tạo của bạn RestAdapter.

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

Cảnh báo: Như Jesse Wilson(từ Square) đề cập ở đây , đây là một lượng sức mạnh nguy hiểm.

Với điều đó đã được nói, tôi chắc chắn nghĩ rằng đây là cách tốt nhất để xử lý một cái gì đó như thế này bây giờ. Nếu bạn có bất kỳ câu hỏi nào, xin vui lòng hỏi trong một bình luận.


2
Làm thế nào để bạn đạt được một cuộc gọi đồng bộ trong Android khi Android không cho phép các cuộc gọi mạng trên luồng chính? Tôi đang gặp vấn đề khi trả lời Phản hồi từ một cuộc gọi không đồng bộ.
lgdroid57

1
@ lgdroid57 Bạn đã đúng, do đó bạn đã có một luồng khác khi bạn bắt đầu yêu cầu ban đầu đã kích hoạt bộ chặn để chạy.
theblang

3
Điều này hoạt động rất tốt ngoại trừ tôi phải đảm bảo đóng phản hồi trước hoặc tôi sẽ rò rỉ kết nối trước đó ... Yêu cầu cuối cùng newRequest = request.newBuilder () .... build (); đáp ứng.body (). close (); return chain.proceed (newRequest);
DallinDyer

Cảm ơn! Tôi đã gặp phải một vấn đề trong đó Cuộc gọi lại của yêu cầu ban đầu đã nhận được thông báo lỗi "đã đóng" thay vì phản hồi ban đầu, do cơ thể bị tiêu thụ trong Thiết bị chặn. Tôi đã có thể sửa lỗi này để trả lời thành công, nhưng tôi không thể sửa lỗi này cho các phản hồi không thành công. Bất kỳ đề xuất?
lgdroid57

Cảm ơn @mattblang, nó trông thật tuyệt. Một câu hỏi: liệu yêu cầu gọi lại có được đảm bảo được gọi ngay cả khi thử lại không?
Luca Fagioli

23

TokenAuthenticator phụ thuộc vào một lớp dịch vụ. Lớp dịch vụ phụ thuộc vào một cá thể OkHttpClient. Để tạo một OkHttpClient tôi cần TokenAuthenticator. Làm thế nào tôi có thể phá vỡ chu kỳ này? Hai OkHttpCl Client khác nhau? Họ sẽ có các nhóm kết nối khác nhau ..

Nếu bạn có, giả sử, một trang bị thêm TokenServicemà bạn cần bên trong Authenticatornhưng bạn chỉ muốn thiết lập một trang OkHttpClientmà bạn có thể sử dụng TokenServiceHolderlàm phụ thuộc cho TokenAuthenticator. Bạn sẽ phải duy trì một tham chiếu đến nó ở cấp ứng dụng (singleton). Điều này thật dễ dàng nếu bạn đang sử dụng Dagger 2, nếu không thì chỉ cần tạo trường lớp bên trong Ứng dụng của bạn.

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

Trong TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

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

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

Thiết lập máy khách:

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

Nếu bạn đang sử dụng Dagger 2 hoặc khung tiêm phụ thuộc tương tự, có một số ví dụ trong câu trả lời cho câu hỏi này


Đâu là TokenServicelớp tạo?
Yogh Suthar

@YogeshSuthar đó là một dịch vụ trang bị thêm - xem câu hỏi liên quan
David Rawson

Cảm ơn, bạn có thể cung cấp việc thực hiện refreshToken()từ service.refreshToken().execute();. Không thể tìm thấy nó thực hiện ở bất cứ đâu.
Yogh Suthar

@Yogesh Phương thức refreshToken là từ API của bạn. Bất cứ điều gì bạn gọi để làm mới mã thông báo (có thể gọi bằng tên người dùng và mật khẩu?). Hoặc có thể yêu cầu bạn gửi mã thông báo và phản hồi là mã thông báo mới
David Rawson

5

Sử dụng TokenAuthenticatorcâu trả lời như @theblang là một cách chính xác để xử lý refresh_token.

Đây là cách triển khai của tôi (Tôi đã sử dụng Kotlin, Dagger, RX nhưng bạn có thể sử dụng ý tưởng này để triển khai cho trường hợp của bạn)
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()
    }
}

Để ngăn chặn chu kỳ phụ thuộc như bình luận @Brais Gabin, tôi tạo 2 giao diện như

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 lớp học

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 lớp học

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

Đánh chặn của tôi

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

Cuối cùng, thêm InterceptorAuthenticatorvào OKHttpClientkhi bạn tạo dịch vụ PotoAuthApi

Bản giới thiệu

https://github.com/PhanVanLinh/AndroidMVPKotlin

Ghi chú

Dòng xác thực
  • API ví dụ getImage()trả về mã lỗi 401
  • authenticatephương pháp bên trong TokenAuthenticatorsẽ bị sa thải
  • Đồng bộ hóa noneAuthAPI.refreshToken(...)được gọi
  • Sau khi noneAuthAPI.refreshToken(...)phản hồi -> mã thông báo mới sẽ thêm vào tiêu đề
  • getImage()sẽ TỰ ĐỘNG được gọi với tiêu đề mới ( HttpLogging S NOT KHÔNG đăng nhập cuộc gọi này) ( interceptbên trong AuthInterceptor SILL KHÔNG GỌI )
  • Nếu getImage()vẫn không thành công với lỗi 401, authenticatephương thức bên trong TokenAuthenticatorsẽ kích hoạt LẠI và LẠI sau đó nó sẽ ném lỗi về phương thức cuộc gọi nhiều lần ( java.net.ProtocolException: Too many follow-up requests). Bạn có thể ngăn chặn nó bằng cách phản ứng đếm . Ví dụ, nếu bạn return nullauthenticatesau 3 lần thử lại, getImage()sẽ hoàn thànhreturn response 401

  • Nếu getImage()phản hồi thành công => chúng tôi sẽ cho kết quả bình thường (như bạn gọi getImage()không có lỗi)

Hy vọng nó sẽ giúp


Giải pháp này sử dụng 2 OkHttpCl Client khác nhau, hiển nhiên trong lớp ServiceGenerator của bạn.
SpecialSnowflower

@ SpecialSnowflower bạn nói đúng. Nếu bạn làm theo giải pháp của tôi, bạn cần tạo 2 OkHttp vì tôi đã tạo 2 dịch vụ (oauth và không auth). Tôi nghĩ rằng nó sẽ không gây ra bất kỳ vấn đề. Hãy cho tôi biết ý tưởng của bạn
Phan Văn Linh

1

Tôi biết đây là một chủ đề cũ, nhưng chỉ trong trường hợp ai đó vấp ngã trong đó.

TokenAuthenticator phụ thuộc vào một lớp dịch vụ. Lớp dịch vụ phụ thuộc vào một cá thể OkHttpClient. Để tạo một OkHttpClient tôi cần TokenAuthenticator. Làm thế nào tôi có thể phá vỡ chu kỳ này? Hai OkHttpCl Client khác nhau? Họ sẽ có các nhóm kết nối khác nhau ..

Tôi đã phải đối mặt với cùng một vấn đề, nhưng tôi chỉ muốn tạo một OkHttpClient vì tôi không nghĩ rằng tôi cần một cái khác cho chính TokenAuthenticator, tôi đã sử dụng Dagger2, vì vậy cuối cùng tôi đã cung cấp lớp dịch vụ khi Lazy tiêm vào TokenAuthenticator, bạn có thể đọc thêm về Lazy tiêm trong dao găm 2 ở đây , nhưng về cơ bản, nó nói với Dagger là KHÔNG đi và tạo dịch vụ cần thiết cho TokenAuthenticator ngay lập tức.

Bạn có thể tham khảo chủ đề SO này cho mã mẫu: Làm thế nào để giải quyết sự phụ thuộc vòng tròn trong khi vẫn sử dụng Dagger2?


0

Bạn có thể thử tạo một lớp cơ sở cho tất cả các trình tải của mình, trong đó bạn sẽ có thể bắt được một ngoại lệ cụ thể và sau đó hành động như bạn cần. Làm cho tất cả các trình tải khác nhau của bạn mở rộng từ lớp cơ sở để truyền bá hành vi.


Trang bị thêm không hoạt động theo cách đó. Nó sử dụng các chú thích java và các giao diện để mô tả một lệnh gọi API
Daniel Zolnai 17/03/2016

Tôi biết cách trang bị thêm hoạt động, nhưng bạn vẫn "gói" các lệnh gọi API của mình bên trong AsynTask phải không?
k3v1n4ud3 17/03/2016

Không, tôi sử dụng các cuộc gọi với một cuộc gọi lại, vì vậy chúng chạy không đồng bộ.
Daniel Zolnai 17/03/2016

Sau đó, bạn có thể có thể tạo một lớp gọi lại cơ sở và làm cho tất cả các cuộc gọi lại của bạn mở rộng nó.
k3v1n4ud3 17/03/14

2
Bất kỳ giải pháp cho điều này? Chính xác là trường hợp của tôi ở đây. = /
Hugo Nogueira

0

Sau thời gian dài nghiên cứu, tôi đã tùy chỉnh máy khách Apache để xử lý Làm mới AccessToken cho trang bị thêm trong đó bạn gửi mã thông báo truy cập dưới dạng tham số.

Bắt đầu Bộ điều hợp của bạn với cookie Khách hàng liên tục

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

Cookie Ứng dụng khách liên tục duy trì cookie cho tất cả các yêu cầu và kiểm tra với từng phản hồi yêu cầu, nếu đó là truy cập trái phép ERROR_CODE = 401, làm mới mã thông báo truy cập và gọi lại yêu cầu, ngoài ra chỉ xử lý yêu cầu.

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

Có lý do nào mà bạn đang sử dụng ApacheClient thay vì giải pháp được đề xuất không? Không phải nó không phải là một giải pháp tốt, nhưng nó cần nhiều mã hóa hơn, so với việc sử dụng Thiết bị chặn.
Daniel Zolnai

Nó được tùy chỉnh để trở thành ứng dụng khách liên tục cookie, duy trì phiên trong suốt các dịch vụ. Ngay cả trong Yêu cầu kiểm tra, bạn có thể thêm accesstoken vào tiêu đề. Nhưng nếu bạn muốn thêm nó như một thông số thì sao? Ngoài ra OKHTTPClient đang có những hạn chế. ref: stackoverflow.com/questions/24594823/
Mạnh

Nó được khái quát hơn để được sử dụng trong mọi trường hợp 1. Cookie Client Persistent Client 2. Chấp nhận các yêu cầu HTTP và HTTPS 3. Cập nhật mã thông báo truy cập trong Params.
Suneel Prakash

0

Sử dụng một Công cụ chặn (tiêm mã thông báo) và một Trình xác thực (thao tác làm mới) thực hiện công việc nhưng:

Tôi cũng gặp vấn đề về cuộc gọi kép: cuộc gọi đầu tiên luôn trả về số 401 : mã thông báo không được thực hiện ở cuộc gọi đầu tiên (bộ chặn) và trình xác thực được gọi: hai yêu cầu đã được thực hiện.

Việc khắc phục chỉ là để tái khẳng định yêu cầu đối với bản dựng trong Thiết bị chặn:

TRƯỚC:

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

SAU:

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

TRONG MỘT LẦN:

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

Hy vọng nó giúp.

Chỉnh sửa: Tôi đã không tìm cách tránh cuộc gọi đầu tiên để luôn trả về 401 chỉ bằng cách sử dụng trình xác thực và không có người chặn


-2

Cho bất cứ ai muốn giải quyết các cuộc gọi đồng thời / song song khi làm mới mã thông báo. Đây là một cách giải quyết

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
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.