我正在使用Retrofit
和OkHttp
库。我有一个Authenticator
,当我们得到401
响应时,它会对用户进行身份验证。
我的build.gradle
是这样的
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
是这样的:
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
课程:
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
的:
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
响应,这意味着一个空响应。因此,我的旧刷新令牌无效,无法获得新令牌。通常情况下,我们的刷新令牌在一年内到期。那我该怎么做呢。请帮帮我!
发布于 2016-02-25 14:56:47
免责声明:实际上,我使用的是
Dagger
+RxJava
+Retrofit
,但我只是想提供一个答案,为未来的访问者演示逻辑。重要:如果您从多个地方发出请求,您的令牌将在TokenAuthenticator
类中刷新多次。例如,当您的活动和服务同时发出请求时。要解决这个问题,只需将synchronized
关键字添加到TokenAuthenticator
的authenticate
方法中即可。在刷新Authenticator
中的令牌时,请发出同步请求,因为您必须阻止该线程,直到请求完成为止,否则您的请求将使用旧的和新的令牌执行两次。在刷新令牌以阻止该线程时,可以使用Schedulers.trampoline()
或blockingGet()
。此外,在authenticate
方法中,您可以通过比较请求令牌和存储的令牌来检查令牌是否已经刷新,以防止不必要的刷新。请不要考虑使用TokenInterceptor
,因为它是边缘情况,而不是每个人,只关注TokenAuthenticator
。
这就是我们正在努力实现的目标:
首先,对于大多数应用程序来说,刷新令牌是一个关键的过程。流程是:如果刷新令牌失败,注销当前用户并要求重新登录.(在注销用户之前,可以重试几次刷新令牌)
--不管怎样,我会一步一步地解释:
步骤1: --请参考单例模式,我们将创建一个类,负责返回我们的翻新实例。因为它是静态的,如果没有可用的实例,它只创建一个实例,当您调用它时,它总是返回这个静态实例。这也是单例设计模式的基本定义。
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
方法中的:
@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
方法,这只是一个您可以创建自己的示例:
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
逻辑的人来说,:
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);
}
}
我在活动和背景服务方面提出要求。它们都使用相同的更新实例,我可以轻松地管理访问令牌。请参考这个答案,并尝试创建您自己的客户。如果你仍然有问题,简单地评论下面,我会尽力帮助。
发布于 2017-04-26 13:28:14
在您的ApiClient.java类中:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(context))
.build();
在您的改造包中添加TokenManager.java类
package co.abc.retrofit;
/**
* Created by ravindrashekhawat on 17/03/17.
*/
public interface TokenManager {
String getToken();
boolean hasToken();
void clearToken();
String refreshToken();
}
在包中添加名为AuthorizationInterceptor.java的拦截器类
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试着理解逻辑。
在底部,有逻辑再次调用相同的请求。
if(accessToken!=null){
modifiedRequest = request.newBuilder()
.addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken())
.build();
return chain.proceed(modifiedRequest);
}
https://stackoverflow.com/questions/35516626
复制相似问题