我正在使用 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
所以我的问题是:有时我会得到一个新的访问 token 并继续工作。但有时我得到一个 400
响应,这意味着一个空响应。所以我的旧刷新 token 无效,我无法获得新 token 。通常我们的刷新 token 会在 1 年内到期。所以我怎么能做到这一点。请帮帮我!
最佳答案
Disclaimer : Actually I am using
Dagger
+RxJava
+Retrofit
but I just wanted to provide an answer to demonstrate logic for future visitors.
Important : If you are making requests from several places your token will refresh multiple times inside
TokenAuthenticator
class. For example when your activity and your service make requests concurrently. To beat this issue just addsynchronized
keyword to yourTokenAuthenticator
sauthenticate
method.
Please make synchronous requests when refreshing your token inside
Authenticator
because you must block that thread until your request finishes, otherwise your requests will be executed twice with old and new tokens. You can useSchedulers.trampoline()
orblockingGet()
when refreshing your token to block that thread.
Also inside
authenticate
method you can check if token is already refreshed by comparing request token with stored token to prevent unnecessary refresh.
And please do not consider using
TokenInterceptor
because it is edge case and not for everyone, just focus onTokenAuthenticator
.
这是我们正在努力实现的目标:
首先,刷新 token 是大多数应用程序的关键过程。 流程是:如果刷新 token 失败,则注销当前用户并要求重新登录。 (可能在注销用户之前重试几次刷新 token )
反正我会一步一步解释的:
第一步:请引用singleton pattern ,我们将创建一个类来负责返回我们的改造实例。因为如果没有可用的实例它是静态的,它只会创建一次实例,当你调用它时总是返回这个静态实例。这也是单例设计模式的基本定义。
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 步:在我的 TokenAuthenticator 的 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);
}
}
我在 Activity 和后台服务中提出请求。它们都使用相同的改造实例,我可以轻松管理访问 token 。请引用此答案并尝试创建您自己的客户端。如果您仍有问题,请在下方评论,我会尽力提供帮助。
关于Android Retrofit2 刷新 Oauth 2 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35516626/