Token有两种,access token和refresh token.
以下三条是token的作用和生命周期过程。
- 用户登录后,去访问其他接口,都需要在http head里带上access token, 但是过期时间通常比较短,一天或几个小时。
- 当access token过期的时候,需要用refresh token重新去获取access token, 同时也刷新refresh token。
- refresh token的过期时间要比access token长很多,几天或几个月。如果一直没有登录,refresh token过期了,就需要强制用户重新登录。
对于双token过期的问题,可以增加一个retrofit拦截器去处理。
拦截器的写法:
public class TokenInterceptor implements Interceptor {
private AtomicBoolean mIsTokenExpired = new AtomicBoolean(false);
private Handler mHandler = new Handler(Looper.getMainLooper());
private static String TAG = TokenInterceptor.class.getSimpleName();
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response proceed = chain.proceed(chain.request());
if (isTokenExpired(proceed)) {
String newToken = getNewToken();
if (!TextUtils.isEmpty(newToken)) {
Request newAgainRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + newToken)
.build();
proceed.close();
return chain.proceed(newAgainRequest);
}
}
return proceed;
}
private synchronized String getNewToken() {
if (mIsTokenExpired.compareAndSet(false, true)) {
OAuthServiceImpl service = new OAuthServiceImpl();
RefreshTokenInfo refreshTokenInfo = new RefreshTokenInfo();
refreshTokenInfo.setRefreshToken(AccountApplication.getInstance().getRefreshToken());
try {
Call<ResponseInfo<TokenInfo>> responseInfoCall = service.refreshToken(refreshTokenInfo);
TokenInfo tokenInfo = get(responseInfoCall);
AccountApplication.getInstance().setAccessToken(tokenInfo.getAccessToken());
AccountApplication.getInstance().setRefreshToken(tokenInfo.getRefreshToken());
return tokenInfo.getAccessToken();
} catch (OAuthException e) {
if (e.getCode().equalsIgnoreCase("401")) {
loginAgain();
}else{
mIsTokenExpired.set(false); //不是因为refresh token过期导致的,可以再尝试刷新。
}
}
return null;
} else {
return AccountApplication.getInstance().getAccessToken();
}
}
protected <T> T get(Call<ResponseInfo<T>> call) throws OAuthException {
try {
Response<ResponseInfo<T>> response = call.execute();
ResponseInfo<T> responseInfo = response.body();
if (responseInfo != null && responseInfo.getCode() != 0) {
Log.i(TAG, this.getClass().getSimpleName() + ", response: " + responseInfo.getCode() + "message: " + responseInfo.getMessage());
throw new OAuthException(responseInfo.getCode() + "", responseInfo.getMessage());
}
if (responseInfo == null) {
Log.i(TAG, this.getClass().getSimpleName() + "responseInfo == null, ErrorConnectFailed");
throw new OAuthException("responseInfo == null, ErrorConnectFailed");
}
return responseInfo.getResult();
} catch (Exception e) {
Log.i(TAG, this.getClass().getSimpleName() + "ErrorConnectFailed" + e.getMessage());
throw new OAuthException("404", e.getMessage());
}
}
private void loginAgain() {
mHandler.post(new Runnable() {
@Override
public void run() {
AccountApplication.getInstance().setLogout();
AccountApplication.getInstance().getContext().startActivity(new Intent(AccountApplication.getInstance().getContext(), LoginActivity.class));
}
});
}
private static boolean isTokenExpired(okhttp3.Response response) {
return response.code() == 401;
}
}
先在response 中判断access token是否过期。过期条件需要和服务端开发人员事先约定好。
当request的access token过期了,就直接去刷新,刷新后再重新发request出去。
以上的方式,优点在于,无需记录双token的过期时间。
在刷新token的过程中,要注意多线程问题。因为很有可能,一下子进来多个token过期的request。我们只需要处理第一个刷新token的过程就可以了。所以同步一下getNewToken()方法。而且刷新的过程也可能由于网络原因刷新失败。所以必须用compare锁一下。
添加到Retrofit:
public class RetrofitManager {
private ConcurrentHashMap<String, BaseService> services = new ConcurrentHashMap<>();
private static RetrofitManager sInstance = null;
private TokenInterceptor mTokenInterceptor = new TokenInterceptor();
private final OkHttpClient mClient = new OkHttpClient.Builder().
connectTimeout(60, TimeUnit.SECONDS).
readTimeout(60, TimeUnit.SECONDS).
writeTimeout(60, TimeUnit.SECONDS).
addInterceptor(mTokenInterceptor)
.build();
private Retrofit mRetrofit = new Retrofit.Builder()
.baseUrl("https://login.nero.com/")
.client(mClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
.build();
.....
}
以上就是处理token过期的方式。
|