前言:我在Android项目中用Retrofit处理token失效,经过不断调试,最终实现了功能。因此,我简单的总结了一下token失效的处理方案,希望对大家有所帮助!
思路:
1.token验证是为了提高app的安全性,需求规定access_token的有效期为1小时,refresh_Token的有效期为7天;
2.如果access_token1个小时后过期了,服务器会返回400或者401,此时客户端要根据刷新access_token的retrofit接口去重新请求新的access_token;
3.如果refresh_Token7天后也过期了,则要求跳到登录页面。
实现步骤:
1.根据刷新的access_token的retrofit接口去重新请求新的access_token
if (response.code() == 400 || response.code() == 401) {
if (!path.contains("/touch")) {//代表不是刷新access_token
//根据后台提供的接口获取新的access_token,比如/api/member/{uuid}/touch?refresh_token=xxxx,如果接口不同,这里需要改动。
String membership_uuid = SharedPreUtil.getString(Global.mContext, "membership_uuid", "");//获取membership_uuid
String refresh_token = SharedPreUtil.getString(Global.mContext, "refresh_token", "");//获取refresh_token
retrofit2.Response<JsonObject> execute = RetrofitManager.getInstance().getHttpService().getNewToken(membership_uuid, refresh_token).execute();
if (execute.code() == 200) {//200代表refresh_token的时间是有效的
JsonObject body = execute.body();
//解析这个jsonBoject,得到新的access_token
String new_token = body.get("access_token").getAsString();
//保存 access_token
SharedPreUtil.saveString(Global.mContext, "access_token", new_token);
LogUtil.e("----------", new_token);
//重新发起请求
Request newRequest = request.newBuilder()
.removeHeader("x-access-token") //移除旧的token
.header("x-access-token", new_token) //添加新的token
.build();
return chain.proceed(newRequest);//重新发起请求,此时是新的token
} else {//代表refresh_token过期
//要求用户直接登录
}
} else {
return response;
}
} else {
//此时没有过期
Log.d("============", "intercept: token ------------");
}
2.需要拦截的接口
if (isMembers(path)) {//凡是接口中包含members都加入拦截器
//加拦截器,x-access-token是access_token的key,这个要问清楚后台
request = request.newBuilder().addHeader("x-access-token", SharedPreUtil.getString(Global.mContext, "access_token", "")).build();
}
/**
*判断刷新token连接,直接返回,不走下面代码,避免死循环
*/
if (request.url().toString().contains("/tokens/") && request.method() == "GET"){
return chain.proceed(request) ;
}
3.接口中包含members的方法
public boolean isMembers(String path) {
if (path.contains("members") && !path.contains("auth")) {
return true;
}
return false;
}
4.token拦截器的完整代码
public class TokenInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String path = request.url().encodedPath();
if (isMembers(path)){ //凡是接口中包含Members都添加到拦截器,如果需求不同,这里需要改动。
//加拦截器,x-access-token是access_token的key,这个要问清楚后台
request = request.newBuilder().addHeader("x-access-token", SharedPreUtil.getString(Global.mContext, "access_token", "")).build();
}
/**
*判断刷新token连接,直接返回,不走下面代码,避免死循环。
*/
if (request.url().toString().contains("/tokens/") && request.method() == "GET"){
return chain.proceed(request) ;
}
//拦截了响应体
Response response = chain.proceed(request);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
if (!HttpHeaders.hasBody(response)) {
//END HTTP
} else if (bodyEncoded(response.headers())) {
//HTTP (encoded body omitted)
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
return response;
}
}
if (!isPlaintext(buffer)) {
return response;
}
if (contentLength != 0) {
//获取到response的body的string字符串
String result = buffer.clone().readString(charset);
//当状态码返回的是400或者401,即代表过期
if (response.code() == 400 || response.code() == 401) {
if (!path.contains("/touch")){//代表不是刷新access_token
//获取新的access_token
String membership_uuid = SharedPreUtil.getString(Global.mContext, "membership_uuid", "");//获取membership_uuid
String refresh_token = SharedPreUtil.getString(Global.mContext, "refresh_token", "");//获取refresh_token
retrofit2.Response<JsonObject> execute = RetrofitManager.getInstance().getHttpService().getNewToken(membership_uuid, refresh_token).execute();
if (execute.code()==200) {
JsonObject body = execute.body();
//解析这个JsonObject,得到新的access_token
String new_token = body.get("access_token").getAsString();
//保存 access_token,覆盖旧的accesss_token
SharedPreUtil.saveString(Global.mContext, "access_token", new_token);
LogUtil.e("----------", new_token);
//重新发起请求
Request newRequest = request.newBuilder()
.removeHeader("x-access-token") //移除旧的token
.header("x-access-token", new_token) //添加新的token
.build();
return chain.proceed(newRequest);//重新发起请求,此时是新的token
}else{
//要求用户直接登录
GotoLoginActivity(Global.mContext);
}
}else {
return response;
}
} else {
//此时没有过期
Log.d("============", "intercept: token ------------");
}
}
}
return response;
}
//要求用户直接登录
private void GotoLoginActivity(Context context) {
AppManager.getInstance().finishOthersActivity(LoginActivity.class);
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
static boolean isPlaintext(Buffer buffer) throws EOFException {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
//项目中所有包含members的接口
public boolean isMembers(String path){
if (path.contains("members") && !path.contains("auth")){
return true;
}
return false;
}
}
5.在RetrofitManager中添加token拦截器
public class RetrofitManager {
...
private void initRetrofit() {
//加入token拦截器
client.interceptors().add(new TokenInterceptor());
...
}
}
6.总结:token验证的好处就是,当用户在下次发送请求的时候,不用再携带用户名和密码,只需要携带Token和相应请求需要带的参数即可。这样可以减轻服务器的负担,也提高安全性。