OkHttp全局刷新token
前言:公司項目採用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的問題
1、新建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到登錄頁面,重新登錄
}
}
}
2、根據與後臺商定的token過期碼,判斷是否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);
//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;
}
3、token過期則進行刷新token
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();
}
4、使用最新的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);
5、關於重複請求token的問題
每一個網絡請求都有一個自己的攔截器,那如何實現當一個網絡請求正在刷新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;
}
}
總結:以上就是我在項目中用的刷新token的方法。如果有不對的地方或者可以改進的地方,麻煩請告知,謝謝!
參考博文:
http://www.jianshu.com/p/8d1ee61bc2d2
http://www.jianshu.com/p/62ab11ddacc8