前言:公司项目采用token验证,要求token失效后,能够自动刷新,并且如果有其他网络请求,能够用这个刷新后的token继续请求数据。
知识介绍:token分为access_token和refresh_token,access_token有效期为2个小时,refresh_token有效期为15天。access_token失效后,需要用refresh_token进行刷新。关于token机制可以看文章基于 Token 的身份验证。
解决方法:
1.通过拦截器,获取返回的数据
2.判断token是否过期
3.如果token过期则刷新token
4.使用最新的token,重新请求网络数据
5.关于重复请求token的问题
public class TokenInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(requestBuilder.build());
if (isTokenExpired(response)) {//根据和服务端的约定判断token过期
String newToken = getNewToken();
if (!TextUtils.isEmpty(newToken)){
//使用新的Token,创建新的请求
Request newRequest = chain.request()
.newBuilder()
.removeHeader(AppConstant.AUTH_HEADER)
.addHeader(AppConstant.AUTH_HEADER, "bearer "+newToken)
.addHeader("Accept","application/json;version=1.2")
.build();
//重新请求
return chain.proceed(newRequest);
} else {
//退出app到登录页面,重新登录
}
}
}
private boolean isTokenExpired(Response response) {
if(response.code() == 401) {
ResponseBody body = response.body();
if (body != null){
try {
MediaType mediaType = body.contentType();
if (mediaType != null) {
if(isText(mediaType)) {
String resp = body.string();
PostOkModel model = new Gson().fromJson(resp, PostOkModel.class);
//4061是与后台商量的token失效后的错误码,具体应根据自己的项目决定
if(model.getCode() == 4601) {
return true;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
private boolean isText(MediaType mediaType)
{
if (mediaType.type() != null && mediaType.type().equals("text"))
{
return true;
}
if (mediaType.subtype() != null)
{
if (mediaType.subtype().equals("json") ||
mediaType.subtype().equals("xml") ||
mediaType.subtype().equals("html") ||
mediaType.subtype().equals("webviewhtml")
)
return true;
}
return false;
}
private static String getNewToken() throws IOException{
String refreshToken = SPDtadUtils.getString(UserApplication.getInstance(), "refreshToken");//之前存储在本地的refreshToken
TokenRefreshPostModel model = new TokenRefreshPostModel();
model.refresh_token = refreshToken;
model.client_id = BuildConfig.CLIENT_ID;//项目中的身份id,根据自己实际情况定
RequestBody body = RequestBody.create(AppConstant.MEDIA_TYPE_JSON, new Gson().toJson(model));
Request request = new Request.Builder().url(AppConstant.TOKEN_REFRESH).post(body).build();
Response newResponse = new OkHttpClient().newCall(request).execute();
Log.e("TAG", "刷新token");
if(newResponse.code() != 200) {
return null;
}
ResponseBody responseBody = newResponse.body();
//以下代码为将token存储到本地
TokenInfoModel tokenInfoModel = new Gson().fromJson(responseBody.string(), TokenInfoModel.class);
UserUtils.saveToken(UserApplication.getInstance(), tokenInfoModel);
SPDtadUtils.putString(UserApplication.getInstance(), "new_token", tokenInfoModel.getAccess_token());
Log.e("TAG", "存储token");
//返回刷新后的token
return tokenInfoModel.getAccess_token();
}
//使用新的Token,创建新的请求
Request newRequest = chain.request()
.newBuilder()
.removeHeader(AppConstant.AUTH_HEADER)
.addHeader(AppConstant.AUTH_HEADER, "bearer "+newToken)
.addHeader("Accept","application/json;version=1.2")
.build();
//重新请求
return chain.proceed(newRequest);
每一个网络请求都有一个自己的拦截器,那如何实现当一个网络请求正在刷新token的时候,其他网路请求需要等待;然后等token刷新后,其他的网路请求再使用这个刷新后的token呢。这时候就要用到java中的同步锁。关于同步锁的具体内容,请查看相关的知识。
加上同步锁以后的最终代码就是一下:
public class TokenInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
String tokenHeader = chain.request().header(ApiConstants.AUTH_HEADER_VALUE);
GlobalDoctorData user = UserApplication.getInstance().getDoctorData();
Request.Builder requestBuilder = chain
.request()
.newBuilder();
if(TextUtils.isEmpty(tokenHeader) && user !=null && !TextUtils.isEmpty(user.getToken())) {
requestBuilder
.removeHeader(AppConstant.AUTH_HEADER)
.addHeader(AppConstant.AUTH_HEADER, user.getToken())
.addHeader("Accept","application/json;version=1.2")
.build();
}
Response response = chain.proceed(requestBuilder.build());
Log.e("TokenInteceptor", "response.code=" + response.code());
if (isTokenExpired(response)) {//根据和服务端的约定判断token过期
Log.e("TokenInteceptor", "静默自动刷新Token,然后重新请求数据");
//同步请求方式,获取最新的Token
SPDtadUtils.putString(UserApplication.getInstance(), "new_token", "");
String newToken = getNewToken();
if(TextUtils.isEmpty(newToken)) {
UserApplication.getInstance().userLogout(true);
throw new IOException(UserApplication.getInstance().getResources().getString(R.string.token_fail));
}else {
//使用新的Token,创建新的请求
Request newRequest = chain.request()
.newBuilder()
.removeHeader(AppConstant.AUTH_HEADER)
.addHeader(AppConstant.AUTH_HEADER, "bearer "+newToken)
.addHeader("Accept","application/json;version=1.2")
.build();
//重新请求
return chain.proceed(newRequest);
}
}
return response;
}
private synchronized static String getNewToken() throws IOException{
Log.e("TAG", "执行上锁");
String refreshToken = SPDtadUtils.getString(UserApplication.getInstance(), "refreshToken");
if(TextUtils.isEmpty(refreshToken)) {
return null;
}
String new_token = SPDtadUtils.getString(UserApplication.getInstance(), "new_token");
if(!TextUtils.isEmpty(new_token)) {
return new_token;
}
TokenRefreshPostModel model = new TokenRefreshPostModel();
model.refresh_token = refreshToken;
model.client_id = BuildConfig.CLIENT_ID;
RequestBody body = RequestBody.create(AppConstant.MEDIA_TYPE_JSON, new Gson().toJson(model));
Request request = new Request.Builder().url(AppConstant.TOKEN_REFRESH).post(body).build();
Response newResponse = new OkHttpClient().newCall(request).execute();
Log.e("TAG", "刷新token");
if(newResponse.code() != 200) {
//退出登录并返回到登录页面的逻辑
SPDtadUtils.putString(UserApplication.getInstance(), "refreshToken", "");
return null;
}
ResponseBody responseBody = newResponse.body();
TokenInfoModel tokenInfoModel = new Gson().fromJson(responseBody.string(), TokenInfoModel.class);
UserUtils.saveToken(UserApplication.getInstance(), tokenInfoModel);
SPDtadUtils.putString(UserApplication.getInstance(), "new_token", tokenInfoModel.getAccess_token());
Log.e("TAG", "存储token");
return tokenInfoModel.getAccess_token();
}
private boolean isTokenExpired(Response response) {
if(response.code() == 401) {
ResponseBody body = response.body();
if (body != null){
try {
MediaType mediaType = body.contentType();
if (mediaType != null) {
if(isText(mediaType)) {
String resp = body.string();
PostOkModel model = new Gson().fromJson(resp, PostOkModel.class);
if(model.getCode() == 4601) {
return true;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
private boolean isText(MediaType mediaType)
{
if (mediaType.type() != null && mediaType.type().equals("text"))
{
return true;
}
if (mediaType.subtype() != null)
{
if (mediaType.subtype().equals("json") ||
mediaType.subtype().equals("xml") ||
mediaType.subtype().equals("html") ||
mediaType.subtype().equals("webviewhtml")
)
return true;
}
return false;
}
}
参考博文:
http://www.jianshu.com/p/8d1ee61bc2d2
http://www.jianshu.com/p/62ab11ddacc8