只要用到请求,那么一定会用到token这个东西
这篇文章主要讲两个方面
1.token失效的处理
目标:我们一定不想在token失效后,直接提示用户重新登录获取新的token
解决步骤:自定义一个Interceptor,用于token失效的处理
import com.avicsafety.lib.tools.Validate;
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.nio.charset.Charset;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
public abstract class TokenInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (isTokenExpired(response)) {//根据和服务端的约定判断token过期
//同步请求方式,获取最新的Token
String newSession = getNewToken();
if (Validate.isNotNull(newSession)) {
//使用新的Token,创建新的请求
Request newRequest = chain.request()
.newBuilder()
.header("access_token", newSession)
.build();
//重新请求
return chain.proceed(newRequest);
}
}
return response;
}
/** * 根据Response,判断Token是否失效 * * @param response * @return */
private boolean isTokenExpired(Response response) {
try {
// 第一种,当header的content-length不确定的情况下会出错
// ResponseBody responseBody = response.peekBody(1024 * 1024);//关键代码
// 第二种
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset UTF8 = Charset.forName("UTF-8");
String json = buffer.clone().readString(UTF8);
// 第三种绝对不行,这个东西一次请求只有一次读取
// BufferedSource source = responseBody.source();
// String json = source.readString(Charset.defaultCharset());
if (StringUtils.isNotBlank(json)) {
Gson gson = new Gson();
BaseResp baseResp = gson.fromJson(json, BaseResp.class);
if (baseResp!=null && 400 == baseResp.getResCode()) {
return true;
}
}
} catch (IOException e) {
return false;
}
// 当返回码为400或者401的时候,我们认定token失效
if (response.code() == 401 || response.code() == 400) {
return true;
}
return false;
}
/** * 同步请求方式,获取最新的Token * * @return */
public abstract String getNewToken() throws IOException;
}
我直接把自定义的HttpUtils也贴出来吧,方便大家使用
```java
package com.avicsafety.lib.OkHttp;
import com.avicsafety.lib.tools.AESUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
public class HttpUtils {
private static HttpUtils retrofitUtils;
private static Retrofit retrofit;
private static Boolean debug = false;
private static String hostName = "";
private static String token = null;
private static TokenExpiredListener tokenExpiredListener = null;
private HttpUtils() {
}
public TokenExpiredListener getTokenExpiredListener() {
return tokenExpiredListener;
}
public void setTokenExpiredListener(TokenExpiredListener tokenExpiredListener) {
HttpUtils.tokenExpiredListener = tokenExpiredListener;
}
/** * 建议在MyApplication中 初始化 * * @param _debug */
public void init(String _hostName, Boolean _debug) {
retrofit = null;
debug = _debug;
hostName = _hostName;
}
/** * 提供服务器地址系信息 * @return */
public String getUrl() {
return hostName;
}
/** * 设置 token 访问时可以携带此token * * @param username * @param password * @param key */
public void setAuthToken(String username, String password, String key) {
reset();
key = AESUtils.initKey(key);
String _token = AESUtils.encrypt(username + "@,@" + password, key);
token = _token;
}
/** * 设置 token 访问时可以携带此token * * @param _token token */
public void setAuthToken(String _token) {
reset();
token = _token;
}
/** * 重新设置 token 和 核心工具 */
public void reset() {
retrofit = null;
token = null;
}
public static HttpUtils getInstance() {
if (retrofitUtils == null) {
synchronized (HttpUtils.class) {
if (retrofitUtils == null) {
retrofitUtils = new HttpUtils();
}
}
}
return retrofitUtils;
}
public String getToken() {
return token;
}
public static synchronized Retrofit getRetrofit() {
if (retrofit == null) {
HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor();
if (debug) {
//显示日志
logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
} else {
logInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
}
HeaderInterceptor headerInterceptor = new HeaderInterceptor(token);
TokenInterceptor tokenInterceptor = new TokenInterceptor() {
@Override
public String getNewToken() throws IOException {
if (tokenExpiredListener != null) {
return tokenExpiredListener.getNewToken();
}
return null;
}
};
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(logInterceptor)
.addInterceptor(headerInterceptor)
.addInterceptor(tokenInterceptor)
//配置SSlSocketFactory
// .sslSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory(), SSLSocketFactoryUtils.createTrustAllManager())
// .hostnameVerifier(new SSLSocketFactoryUtils.TrustAllHostnameVerifier())
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS).build();
Gson gson = new GsonBuilder()
//解决map Double 问题
.registerTypeAdapter(Map.class,
new JsonDeserializer<Map<String, Object>>() {
@Override
public Map<String, Object> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
Map treeMap = new HashMap<>();
JsonObject jsonObject = json.getAsJsonObject();
Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
for (Map.Entry<String, JsonElement> entry : entrySet) {
if (entry.getValue().isJsonArray()) {
treeMap.put(entry.getKey(), entry.getValue());
} else {
treeMap.put(entry.getKey(), entry.getValue().getAsString());
}
}
return treeMap;
}
})
.registerTypeAdapter(Date.class, new DateAdapterNull())
//解决 日期格式问题
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
retrofit = new Retrofit.Builder().baseUrl(hostName).client(httpClient)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
// retrofit = new Retrofit.Builder().baseUrl(hostName).client(httpClient).addConverterFactory(JacksonConverterFactory.create())
// .build();
}
return retrofit;
}
public static class NullOnEmptyConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new Converter<ResponseBody,Object>() {
@Override
public Object convert(ResponseBody body) throws IOException {
if (body.contentLength() == 0) return null;
return delegate.convert(body);
}
};
}
}
/*** * 读取*.cer公钥证书文件, 获取公钥证书信息 * @author xgh */
public static void testReadX509CerFile(InputStream inStream) throws Exception {
try {
// 读取证书文件
// 创建X509工厂类
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// 创建证书对象
X509Certificate oCert = (X509Certificate) cf
.generateCertificate(inStream);
inStream.close();
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy/MM/dd");
String info = null;
// 获得证书版本
info = String.valueOf(oCert.getVersion());
System.out.println("证书版本:" + info);
// 获得证书序列号
info = oCert.getSerialNumber().toString(16);
System.out.println("证书序列号:" + info);
// 获得证书有效期
Date beforedate = oCert.getNotBefore();
info = dateformat.format(beforedate);
System.out.println("证书生效日期:" + info);
Date afterdate = oCert.getNotAfter();
info = dateformat.format(afterdate);
System.out.println("证书失效日期:" + info);
// 获得证书主体信息
info = oCert.getSubjectDN().getName();
System.out.println("证书拥有者:" + info);
// 获得证书颁发者信息
info = oCert.getIssuerDN().getName();
System.out.println("证书颁发者:" + info);
// 获得证书签名算法名称
info = oCert.getSigAlgName();
System.out.println("证书签名算法:" + info);
} catch (Exception e) {
System.out.println("解析证书出错!");
e.printStackTrace();
}
}
private static class DateAdapterNull implements JsonSerializer {
//数据返回时date转成json字符
@Override
public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) {
return new JsonPrimitive("");
} else {
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return new JsonPrimitive(formatter.format(src));
}
}
}
public <T> T getApiService(Class<T> clazz) {
Retrofit retrofit = getRetrofit();
return retrofit.create(clazz);
}
public interface TokenExpiredListener {
String getNewToken() throws IOException;
}
}
代码不多,我也就不一一讲解了
这里主要说一下第二点问题,就是这个token失效被服务器端定义在内部业务代码中,怎么办呢
办法很简单,我们直接取出来,判断一下就可以了
嗯嗯,道理是这样的,但当你从response中直接拿出ResponseBody时,你会发现错误出现了
End of input at line 1 column 1 path $
为什么呢,OkHttp不把它存储在内存中,就是你需要的时候就去读一次 只给你了内容,没有给引用,所以一次请求读一次,打印body后原ResponseBody会被清空,response中的流会被关闭,程序会报错,我们需要创建出一个新的ResponseBody 给应用层处理。
那么我们就会用到以下代码
try {
// 第一种,当header的content-length不确定的情况下会出错
// ResponseBody responseBody = response.peekBody(1024 * 1024);//关键代码
// 第二种
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset UTF8 = Charset.forName("UTF-8");
String json = buffer.clone().readString(UTF8);
// 第三种绝对不行,这个东西一次请求只有一次读取
// BufferedSource source = responseBody.source();
// String json = source.readString(Charset.defaultCharset());
if (StringUtils.isNotBlank(json)) {
Gson gson = new Gson();
BaseResp baseResp = gson.fromJson(json, BaseResp.class);
if (baseResp!=null && 400 == baseResp.getResCode()) {
return true;
}
}
} catch (IOException e) {
e.printStackTrace();
return false;
}