在App执行网络请求的场景中,经常需要用到Token作为合法访问身份识别码,设置到Header中传递给后台。且token往往是有效期的,这个时候,就需要用到 OkHttp的拦截器,对请求是否需要携带token做验证,并对token是否有效做验证,且在必要的时候重新获取合法token。
直接进入正题,token拦截器一共做了两件事情:1,对请求接口做判断,是否需要携带token;2,对token合法性做判断,并获取合法token。
1,判断请求接口是否需要携带token
(1)抽象拦截器,自定义用于判断是否需要token的Header字段:
public abstract class RxTokenInterceptor implements Interceptor {
/**
* IService接口中添加header,用于表示该接口是否必须携带token
*/
public static final String HEADER_NEED_TOKEN = "NeedToken: true";
public static final String HEADER_NO_NEED_TOKEN = "NeedToken: false";
// ..............
}(2)声明接口时候,加入自定义header标识
@FormUrlEncoded
@Headers(TokenInterceptor.HEADER_NO_NEED_TOKEN)
@POST("api/v3/login")
Observable<Response<UserInfo>> loginByPassword(@Field("username") String userName,
@Field("password") String password);
@FormUrlEncoded
@Headers(TokenInterceptor.HEADER_NEED_TOKEN)
@POST("api/v3/login/password")
Observable<Response<Object>> setPassword(@Field("phoneNum") String phoneNum,
@Field("password") String password,
@Field("confirmPwd") String confirmPwd);(3)抽象拦截器截取自定义header标识,做判断。并开放getToken抽象方法给子类。
public abstract class RxTokenInterceptor implements Interceptor {
/**
* IService接口中添加header,用于表示该接口是否必须携带token
*/
public static final String HEADER_NEED_TOKEN = "NeedToken: true";
public static final String HEADER_NO_NEED_TOKEN = "NeedToken: false";
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original
.newBuilder();
String needToken = original.header("NeedToken");
if (needToken == null || needToken.equals("true")) {
builder.header("Authorization", getToken());
}
Request request = builder
.removeHeader("NeedToken")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
protected abstract String getToken();
}2,对token合法性做判断,并获取合法token。
Token拦截规则:
1.当前未登录:取消正在执行的所有请求,跳转登录;
2.当前已登录:
(1)token未过期:返回token
(2)token已过期:重新刷token
1)刷新成功:更新本地token并返回
2)刷新失败:取消正在执行的所有请求,弹窗重登陆 -> 跳转登录;
为保证 取消正在执行的所有请求 到 跳转登录页 过程中另外有新的请求产生,导致多次跳转登录页情况,建议把LoginActivity设置SingleTop
为保证 多个请求同时发生,且都因Token过期而触发重复刷新Token的情况,可以考虑给刷新token操作加同步锁。
直接上源码:
/**
* Token拦截:
* 1.当前未登录:取消正在执行的所有请求,跳转登录;
* 2.当前已登录:
* (1)token未过期:返回token
* (2)token已过期:重新刷token
* 1)刷新成功:更新本地token并返回
* 2)刷新失败:取消正在执行的所有请求,弹窗重登陆 -> 跳转登录;
* 为保证 取消正在执行的所有请求 到 跳转登录页 过程中另外有新的请求产生,导致多次跳转登录页情况,建议把LoginActivity设置SingleTop
*
* @author Created by qiang.hou on 2018/12/11.
* @version 1.0
*/
public class TokenInterceptor extends RxTokenInterceptor {
@Override
protected String getToken() {
String tokenValue = "";
if (!isLoginStatus()) {
// 取消正在执行的所有请求
RxRetrofitUtils.getInstance().cancelAll();
// go to login
return tokenValue;
}
if (isExpires()) {
Call<BeanForRefreshToken> tokenCall = RxRetrofitUtils.getInstance()
.create(ICommonService.class)
.refreshToken("refresh_token",
SharedManager.getInstance().getValue(SharedManager.KEY_REFRESH_TOKEN_VALUE, ""));
BeanForRefreshToken data = null;
try {
data = tokenCall.execute().body();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (data != null) {
SharedManager.getInstance().refreshToken(data);
tokenValue = data.tokenType + " " + data.value;
} else {
//获取不到token直接清除登录信息
SharedManager.getInstance().clearUserData();
// 取消正在执行的所有请求
RxRetrofitUtils.getInstance().cancelAll();
// show dialog and goto login
showDialogAndGoToLogin();
}
}
} else {
tokenValue = SharedManager.getInstance().getValue(SharedManager.KEY_TOKEN_TYPE, "")
+ " "
+ SharedManager.getInstance().getValue(SharedManager.KEY_TOKEN_VALUE, "");
}
return tokenValue;
}
/**
* show goto login dialog
*/
private void showDialogAndGoToLogin() {
}
/**
* token是否过了有效期
*
* @return boolean
*/
private boolean isExpires() {
long expiresTime = SharedManager.getInstance().getValue(SharedManager.KEY_TOKEN_EXPIRES_TIME, 0L);
return System.currentTimeMillis() > expiresTime;
}
/**
* 是否处于登录状态
*
* @return boolean
*/
private boolean isLoginStatus() {
return false;
}
}
版权声明:本文为qingjuyashi原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。