首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >安卓Retrofit2刷新Oauth 2令牌

安卓Retrofit2刷新Oauth 2令牌
EN

Stack Overflow用户
提问于 2016-02-19 23:00:05
回答 2查看 30.8K关注 0票数 31

我正在使用RetrofitOkHttp库。我有一个Authenticator,当我们得到401响应时,它会对用户进行身份验证。

我的build.gradle是这样的

代码语言:javascript
运行
复制
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.okhttp3:okhttp:3.1.2'

我的Authenticator是这样的:

代码语言:javascript
运行
复制
public class CustomAuthanticator  implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
    
    //refresh access token
    refreshTokenResult=apiService.refreshUserToken(parameters);
    //this is synchronous retrofit request
    RefreshTokenResult refreshResult = refreshTokenResult.execute().body();
    //check if response equals 400, means empty response
    if(refreshResult != null) {
        // save new access and refresh token
        // then create a new request and new access token as header
        return response.request().newBuilder()
                .header("Authorization", newaccesstoken)
                .build();

    } else {
        // we got empty response and we should return null
        // if we don't return null
        // this method will try to make so many requests to get new access token
        return null;
    }
                    
}}

这是我的APIService课程:

代码语言:javascript
运行
复制
public interface APIService {

@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST("token")
public Call<RefreshTokenResult> refreshUserToken(@Header("Accept") String accept, 
    @Header("Content-Type") String contentType, @Field("grant_type") String grantType,
    @Field("client_id") String clientId, @Field("client_secret") String clientSecret, 
    @Field("refresh_token") String refreshToken);
}

我是这样使用Retrofit的:

代码语言:javascript
运行
复制
CustomAuthanticator customAuthanticator=new CustomAuthanticator();
OkHttpClient okClient = new OkHttpClient.Builder()
        .authenticator(customAuthanticator)
        .build();
Retrofit client = new Retrofit.Builder()
        .baseUrl(getResources().getString(R.string.base_api_url))
        .addConverterFactory(GsonConverterFactory.create(gson))
        .client(okClient)
        .build();
    
//then make retrofit request

所以我的问题是:有时会得到一个新的访问令牌并继续工作。但有时我会得到一个400响应,这意味着一个空响应。因此,我的旧刷新令牌无效,无法获得新令牌。通常情况下,我们的刷新令牌在一年内到期。那我该怎么做呢。请帮帮我!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-02-25 14:56:47

免责声明:实际上,我使用的是Dagger +RxJava + Retrofit,但我只是想提供一个答案,为未来的访问者演示逻辑。重要:如果您从多个地方发出请求,您的令牌将在TokenAuthenticator类中刷新多次。例如,当您的活动和服务同时发出请求时。要解决这个问题,只需将synchronized关键字添加到TokenAuthenticatorauthenticate方法中即可。在刷新Authenticator中的令牌时,请发出同步请求,因为您必须阻止该线程,直到请求完成为止,否则您的请求将使用旧的和新的令牌执行两次。在刷新令牌以阻止该线程时,可以使用Schedulers.trampoline()blockingGet()。此外,在authenticate方法中,您可以通过比较请求令牌和存储的令牌来检查令牌是否已经刷新,以防止不必要的刷新。请不要考虑使用TokenInterceptor,因为它是边缘情况,而不是每个人,只关注TokenAuthenticator

这就是我们正在努力实现的目标:

首先,对于大多数应用程序来说,刷新令牌是一个关键的过程。流程是:如果刷新令牌失败,注销当前用户并要求重新登录.(在注销用户之前,可以重试几次刷新令牌)

--不管怎样,我会一步一步地解释:

步骤1: --请参考单例模式,我们将创建一个类,负责返回我们的翻新实例。因为它是静态的,如果没有可用的实例,它只创建一个实例,当您调用它时,它总是返回这个静态实例。这也是单例设计模式的基本定义。

代码语言:javascript
运行
复制
public class RetrofitClient {

private static Retrofit retrofit = null;

private RetrofitClient() {
    // private constructor to prevent access
    // only way to access: Retrofit client = RetrofitClient.getInstance();
}

public static Retrofit getInstance() {
    if (retrofit == null) {
        // TokenAuthenticator can be singleton too
        TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();

        // !! This interceptor is not required for everyone !!
        // Main purpose of this interceptor is to reduce server calls

        // Our token needs to be refreshed after 10 hours
        // We open our app after 50 hours and try to make a request.
        // Of course token is expired and we will get a 401 response.
        // So this interceptor checks time and refreshes token beforehand.
        // If this fails and I get 401 then my TokenAuthenticator does its job.
        // if my TokenAuthenticator fails too, basically I just logout the user.
        TokenInterceptor tokenInterceptor = new TokenInterceptor();

        OkHttpClient okClient = new OkHttpClient.Builder()
                .authenticator(tokenAuthenticator)
                .addInterceptor(tokenInterceptor)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(base_api_url)
                .client(okClient)
                .build();
    }
    return retrofit;
  }
}

步骤2:我的令牌身份验证器的authenticate方法中的

代码语言:javascript
运行
复制
@Override
public synchronized Request authenticate(Route route, Response response) throws IOException {

    boolean refreshResult = refreshToken();
    if (refreshResult) {
    // refresh token is successful, we saved new token to storage.
    // Get your token from storage and set header
    String newaccesstoken = "your new access token";

    // execute failed request again with new access token
    return response.request().newBuilder()
            .header("Authorization", newaccesstoken)
            .build();

    } else {
        // Refresh token failed, you can logout user or retry couple of times
        // Returning null is critical here, it will stop the current request
        // If you do not return null, you will end up in a loop calling refresh
        return null;
    }
}

refreshToken方法,这只是一个您可以创建自己的示例:

代码语言:javascript
运行
复制
public boolean refreshToken() {
    // you can use RxJava with Retrofit and add blockingGet
    // it is up to you how to refresh your token
    RefreshTokenResult result = retrofit.refreshToken();
    int responseCode = result.getResponseCode();

    if(responseCode == 200) {
        // save new token to sharedpreferences, storage etc.
        return true;
    } else {
        //cannot refresh
        return false;
    } 
}

步骤3:对于那些希望看到TokenInterceptor逻辑的人来说,

代码语言:javascript
运行
复制
public class TokenInterceptor implements Interceptor {
SharedPreferences prefs;
SharedPreferences.Editor prefsEdit;

@Override
public Response intercept(Chain chain) throws IOException {

    Request newRequest = chain.request();

    // get expire time from shared preferences
    long expireTime = prefs.getLong("expiretime",0);
    Calendar c = Calendar.getInstance();
    Date nowDate = c.getTime();
    c.setTimeInMillis(expireTime);
    Date expireDate = c.getTime();

    int result = nowDate.compareTo(expireDate);
    // when comparing dates -1 means date passed so we need to refresh token
    if(result == -1) {
        //refresh token here , and get new access token
        TokenResponse tokenResponse = refreshToken();

        // Save refreshed token's expire time :
        integer expiresIn = tokenResponse.getExpiresIn();
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND,expiresIn);
        prefsEdit.putLong("expiretime",c.getTimeInMillis());

        String newaccessToken = "new access token";
        newRequest=chain.request().newBuilder()
                .header("Authorization", newaccessToken)
                .build();
    }
    return chain.proceed(newRequest);
  }
}

我在活动和背景服务方面提出要求。它们都使用相同的更新实例,我可以轻松地管理访问令牌。请参考这个答案,并尝试创建您自己的客户。如果你仍然有问题,简单地评论下面,我会尽力帮助。

票数 47
EN

Stack Overflow用户

发布于 2017-04-26 13:28:14

在您的ApiClient.java类中:

代码语言:javascript
运行
复制
OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(new AuthorizationInterceptor(context))
                .build();

在您的改造包中添加TokenManager.java

代码语言:javascript
运行
复制
package co.abc.retrofit;

/**
 * Created by ravindrashekhawat on 17/03/17.
 */

public interface TokenManager {
    String getToken();
    boolean hasToken();
    void clearToken();
    String refreshToken();
}

在包中添加名为AuthorizationInterceptor.java的拦截器类

代码语言:javascript
运行
复制
package co.smsmagic.retrofit;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;

import com.google.gson.Gson;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

import co.abc.models.RefreshTokenResponseModel;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
import retrofit2.http.Header;

import static co.abc.utils.abcConstants.ACCESS_TOKEN;
import static co.abc.utils.abcConstants.BASE_URL;
import static co.abc.utils.abcConstants.GCM_TOKEN;
import static co.abc.utils.abcConstants.JWT_TOKEN_PREFIX;
import static co.abc.utils.abcConstants.REFRESH_TOKEN;

/**
 * Created by ravindrashekhawat on 21/03/17.
 */

public class AuthorizationInterceptor implements Interceptor {
    private static Retrofit retrofit = null;
    private static String deviceToken;
    private static String accessToken;
    private static String refreshToken;
    private static TokenManager tokenManager;
    private static Context mContext;

    public AuthorizationInterceptor(Context context) {
        this.mContext = context;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request modifiedRequest = null;

        tokenManager = new TokenManager() {
            final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);

            @Override
            public String getToken() {

                accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
                return accessToken;
            }

            @Override
            public boolean hasToken() {
                accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
                if (accessToken != null && !accessToken.equals("")) {
                    return true;
                }
                return false;
            }

            @Override
            public void clearToken() {
                sharedPreferences.edit().putString(ACCESS_TOKEN, "").apply();
            }

            @Override
            public String refreshToken() {
                final String accessToken = null;

                RequestBody reqbody = RequestBody.create(null, new byte[0]);
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder()
                        .url(BASE_URL + "refresh")
                        .method("POST", reqbody)
                        .addHeader("Authorization", JWT_TOKEN_PREFIX + refreshToken)
                        .build();

                try {
                    Response response = client.newCall(request).execute();
                    if ((response.code()) == 200) {
                        // Get response
                        String jsonData = response.body().string();

                        Gson gson = new Gson();
                        RefreshTokenResponseModel refreshTokenResponseModel = gson.fromJson(jsonData, RefreshTokenResponseModel.class);
                        if (refreshTokenResponseModel.getRespCode().equals("1")) {
                            sharedPreferences.edit().putString(ACCESS_TOKEN, refreshTokenResponseModel.getResponse()).apply();
                            return refreshTokenResponseModel.getResponse();
                        }

                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return accessToken;
            }
        };

        final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
        deviceToken = sharedPreferences.getString(GCM_TOKEN, "");
        accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
        refreshToken = sharedPreferences.getString(REFRESH_TOKEN, "");

        Response response = chain.proceed(request);
        boolean unauthorized =false;
        if(response.code() == 401 || response.code() == 422){
            unauthorized=true;
        }

        if (unauthorized) {
            tokenManager.clearToken();
            tokenManager.refreshToken();
            accessToken = sharedPreferences.getString(ACCESS_TOKEN, "");
            if(accessToken!=null){
                modifiedRequest = request.newBuilder()
                        .addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken())
                        .build();
                return chain.proceed(modifiedRequest);
            }
        }
        return response;
    }
}

注意: --这是我提供的刷新令牌的工作代码,请保持冷静,您只需更改一些常量,除非它会工作,perfectly.Just试着理解逻辑。

在底部,有逻辑再次调用相同的请求。

代码语言:javascript
运行
复制
 if(accessToken!=null){
                modifiedRequest = request.newBuilder()
                        .addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken())
                        .build();
                return chain.proceed(modifiedRequest);
  }
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35516626

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档