Android Retrofit2 刷新 Oauth 2 令牌 [英] Android Retrofit2 Refresh Oauth 2 Token
问题描述
我正在使用 Retrofit
和 OkHttp
库.我有一个 Authenticator
可以在我们收到 401
响应时对用户进行身份验证.
我的build.gradle
是这样的:
编译'com.squareup.retrofit2:retrofit:2.0.0-beta4'编译'com.squareup.retrofit2:converter-gson:2.0.0-beta4'编译'com.squareup.okhttp3:okhttp:3.1.2'
而我的Authenticator
是这样的:
public class CustomAuthanticator 实现了 Authenticator {@覆盖公共请求身份验证(路由路由,响应响应)抛出 IOException {//刷新访问令牌refreshTokenResult=apiService.refreshUserToken(参数);//这是同步改造请求RefreshTokenResult refreshResult = refreshTokenResult.execute().body();//检查响应是否等于400,表示空响应if(refreshResult != null) {//保存新的访问和刷新令牌//然后创建一个新的请求和新的访问令牌作为标头返回 response.request().newBuilder().header(授权", newaccesstoken).建造();} 别的 {//我们得到了空响应,我们应该返回 null//如果我们不返回 null//此方法将尝试发出如此多的请求以获取新的访问令牌返回空;}}}
这是我的 APIService
类:
公共接口 APIService {@FormUrlEncoded@Headers(缓存控制:无缓存")@POST(令牌")公共调用refreshUserToken(@Header("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).建造();改造客户端 = new Retrofit.Builder().baseUrl(getResources().getString(R.string.base_api_url)).addConverterFactory(GsonConverterFactory.create(gson)).client(okClient).建造();//然后提出改造请求
所以我的问题是:有时我会获得一个新的访问令牌并继续工作.但有时我会收到 400
响应,这意味着一个空响应.所以我的旧刷新令牌无效,我无法获得新令牌.通常我们的刷新令牌会在 1 年内到期.那么我该如何做到这一点.请帮帮我!
免责声明:实际上我正在使用
Dagger
+RxJava
+Retrofit
但我只是想提供一个答案来为未来的访问者演示逻辑.
<块引用>
重要:如果您从多个地方发出请求,您的令牌将在 TokenAuthenticator
类中多次刷新.例如,当您的活动和服务同时发出请求时.要解决这个问题,只需将 synchronized
关键字添加到您的 TokenAuthenticator
的 authenticate
方法.
<块引用>
请在 Authenticator
中刷新令牌时发出同步请求,因为您必须阻塞该线程直到请求完成,否则您的请求将使用旧令牌和新令牌执行两次.您可以在刷新令牌以阻止该线程时使用 Schedulers.trampoline()
或 blockingGet()
.
<块引用>
同样在 authenticate
方法中,您可以通过比较请求令牌和存储的令牌来检查令牌是否已经刷新,以防止不必要的刷新.
<块引用>
并且请不要考虑使用TokenInterceptor
,因为它是边缘情况,并不适合所有人,只需关注TokenAuthenticator
.
这就是我们正在努力实现的目标:
首先刷新令牌是大多数应用程序的关键过程.流程是:如果刷新令牌失败,则注销当前用户并要求重新登录.(可能在注销用户之前重试刷新令牌几次)
反正我会一步一步解释:
第一步:请参考单例模式,我们将创建一个负责返回我们的改造实例的类.由于它是静态的,如果没有可用的实例,它只会创建一次实例,并且在您调用它时总是返回这个静态实例.这也是单例设计模式的基本定义.
公共类 RetrofitClient {私有静态改造改造 = null;私人改造客户端(){//防止访问的私有构造函数//唯一访问方式:Retrofit client = RetrofitClient.getInstance();}公共静态改造 getInstance() {如果(改造==空){//TokenAuthenticator 也可以是单例的TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();//!!这个拦截器不是每个人都需要的!!//这个拦截器的主要目的是减少服务器调用//我们的令牌需要在 10 小时后刷新//我们在 50 小时后打开我们的应用程序并尝试发出请求.//当然令牌已过期,我们将收到 401 响应.//所以这个拦截器会预先检查时间并刷新令牌.//如果失败并且我得到 401,那么我的 TokenAuthenticator 完成它的工作.//如果我的 TokenAuthenticator 也失败了,基本上我只是注销用户.TokenInterceptor tokenInterceptor = new TokenInterceptor();OkHttpClient okClient = new OkHttpClient.Builder().authenticator(tokenAuthenticator).addInterceptor(tokenInterceptor).建造();改造 = 新改造.Builder().baseUrl(base_api_url).client(okClient).建造();}返回改造;}}
第 2 步:在我的 TokenAuthenticator 的 authenticate
方法中:
@Override公共同步请求身份验证(路由路由,响应响应)抛出 IOException {boolean refreshResult = refreshToken();如果(刷新结果){//刷新令牌成功,我们将新令牌保存到存储中.//从存储中获取您的令牌并设置标头String newaccesstoken = "你的新访问令牌";//使用新的访问令牌再次执行失败的请求返回 response.request().newBuilder().header(授权", newaccesstoken).建造();} 别的 {//刷新令牌失败,您可以注销用户或重试几次//这里返回 null 很关键,它会停止当前的请求//如果你不返回null,你将进入一个循环调用refresh返回空;}}
还有 refreshToken
方法,这只是一个你可以创建自己的例子:
public boolean refreshToken() {//你可以在 Retrofit 中使用 RxJava 并添加 blocksGet//如何刷新令牌取决于您RefreshTokenResult 结果 = retrofit.refreshToken();int responseCode = result.getResponseCode();如果(响应代码 == 200){//将新令牌保存到 sharedpreferences、storage 等.返回真;} 别的 {//无法刷新返回假;}}
第3步:对于那些想看TokenInterceptor
逻辑的人:
public class TokenInterceptor 实现 Interceptor {SharedPreferences 首选项;SharedPreferences.Editor prefsEdit;@覆盖公共响应拦截(链链)抛出 IOException {请求 newRequest = chain.request();//从共享首选项中获取过期时间long expireTime = prefs.getLong(expiretime",0);日历 c = Calendar.getInstance();日期 nowDate = c.getTime();c.setTimeInMillis(expireTime);日期 expireDate = c.getTime();int result = nowDate.compareTo(expireDate);//比较日期时 -1 表示日期已过,因此我们需要刷新令牌如果(结果 == -1){//在这里刷新令牌,并获得新的访问令牌TokenResponse tokenResponse = refreshToken();//保存刷新令牌的过期时间:整数 expiresIn = tokenResponse.getExpiresIn();日历 c = Calendar.getInstance();c.add(Calendar.SECOND,expiresIn);prefsEdit.putLong("expiretime",c.getTimeInMillis());String newaccessToken = "新访问令牌";newRequest=chain.request().newBuilder().header(授权", newaccessToken).建造();}返回 chain.proceed(newRequest);}}
我在活动和后台服务中提出请求.他们都使用相同的改造实例,我可以轻松管理访问令牌.请参考此答案并尝试创建您自己的客户端.如果您仍有问题,请在下方评论,我会尽力提供帮助.
I am using Retrofit
and OkHttp
libraries. I have an Authenticator
that authenticates the user when we get a 401
response.
My build.gradle
is like this:
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'
And my Authenticator
is like this:
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;
}
}}
This is my APIService
class :
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);
}
I am using Retrofit
like this:
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
So my question is: Sometimes I get a new access token and continue work. But sometimes I get a 400
response which means an empty response. So my old refresh token is invalid and I can't get a new token. Normally our refresh token expires in 1 year. So how I can do this. Please help me!
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
.
This is what we are trying to achieve:
First of all refreshing token is a critical process for most apps. The flow is: If refresh token fails, logout current user and require to re-login. (Maybe retry refresh token couple of times before logging out the user)
Anyways I will explain it step by step:
Step 1: Please refer singleton pattern, we will create one class that's responsible for returning our retrofit instance. Since it is static if there is no instance available it just creates instance only once and when you call it always returns this static instance. This is also basic definition of Singleton design 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;
}
}
Step 2: In my TokenAuthenticator's authenticate
method :
@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;
}
}
And refreshToken
method, this is just an example you can create your own:
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;
}
}
Step 3: For those who wants to see TokenInterceptor
logic:
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);
}
}
I am making requests at activities and background services. All of them uses the same retrofit instance and I can easily manage access token. Please refer to this answer and try to create your own client. If you still have issues simply comment below, I'll try to help.
这篇关于Android Retrofit2 刷新 Oauth 2 令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!