科学的解决Http Token拦截器TokenInterceptor实现
1.写在前面
在做项目的时候,有时对接口要求比较严谨。先介绍下情况。
- 我这边Http 方式采用的是 OKhttp+Retrofit
- 后台一共分为三种token,分别是实名token(accessToken),匿名token(oauthToken),刷新token(refreshToken),不同的token用途不一样,有的作为请求参数放在请求体中,有的作为头部。
2.解决
如果你和我情况差不多,那恭喜你,可以通过这篇博客解决你的一些问题;如果不是,也可以瞧瞧,可能也能得到一些启发。
先把oauthToken拿出来单独说,因为它比较简单,只是单独注册或者忘记密码使用,一般来说后台会给个单独的get请求接口用于获取oauthToen,然后我们对它进行sp缓存,并且一般来说作为它会作为请求参数放在请求体中作为注册或者忘记密码的参数,注意这里是参数而不是头部,特别要注意,一般后台都会这样设计,所以,此token比较简单。
一般来说accessToken是会作为头部放在Http请求中,比如我们的Header规则是:
key—>Authorization
value–>token_type access_token
相信大家都看得懂,不一样的是,我们的值不仅仅是个accessToken,而是一个其他变量和它拼接而成的,当然这个影响不大,因为这些值都会在我们登录成功后返回给我们,我们只需要在登录成功后用SP将他们缓存即可,所以这里我的头部配置代码如下:
//添加头部token
.addHeader("Authorization",getDefaultTokenType()+" "+SPUtils.getSharedStringData(BaseApplication.getAppContext(),AppConstant.ACCESS_TOKEN_KEY)) /**
* 从Sp里面取TokenType,如果没有则返回默认确定的值
* @return
*/
public String getDefaultTokenType(){
String tokenType = SPUtils.getSharedStringData(BaseApplication.getAppContext(), AppConstant.TOKEN_TYPE_KEY);
if(TextUtils.isEmpty(tokenType)){
tokenType = "token";
//最好保存在SP中
SPUtils.setSharedStringData(BaseApplication.getAppContext(), AppConstant.TOKEN_TYPE_KEY, tokenType);
return tokenType;
} return tokenType;
}
这应该算第一步,根据后台定义的规则,配置好头部。有些人担心,有些接口请求并不需要头部,我们配置了有没有影响,比如登录接口,我们都没有获取到accessToken,怎么会请求需要头部呢。这里不用担心,没事的。
我们知道所有的HTTP响应行中有自己的响应码,比如常见的404等等,这里后台给我规定了只要判断200,401和其他。
- 200表示请求成功,前端不用做什么多余的处理,直接获取数据解析即可;
- 401是后台规定返回给我的,可能每个项目后台返回的不一样,这里后台是可以控制的,具体可能不一样,但是影响不大。当出现这种情况时,我们前端需要做的事情就比较多了,从拦截的响应体中解析,注意,就算报401,后台也会返回给我数据的,比如下面是我们返回的json数据;
- 只要获取到响应json数据,然后解析,判断之类的就是了,这里比较简单;
- 然后其他的万一304、500那些直接弹出Toast。
现在只剩下一个重要问题,就是我们怎么知道这接口啥时候是200,啥时候是401,我们先假设在最后返回中进行判断,比如假设是OKHTTP,那么就是在onResponse中,我们需要先拿到RresponseCode,然后在根据不同的code,写不同的bean做解析,如果是用了Retrofit,这太不符合实际项目情况了,先不说我们项目这中网络请求框架封装的死死的,一般来说直接写好bean,直接就解析出bean了,这里效率慢了太多了,因为我们后面如果在401的情况下,再解析status状态码,如果需要刷新token,又要请求接口,所以这里我们需要用拦截器。
拦截器在我使用以后越来越感觉太强大了,首先它既可以拦截请求数据,比如请求行,请求头,请求体,同理也可以拦截响应行,响应头,响应体也可以获取到,最主要的是我们可以操作Request和Response。
用代码说话,先试用下对okhttpclient配置。
.addInterceptor(new TokenInterceptor()) //添加token拦截器
实现Interceptor接口,在intercept方法中写上如下代码。
Request request = chain.request(); long t1 = System.nanoTime(); LogUtils.logd(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request); long t2 = System.nanoTime();
LogUtils.logd(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
LogUtils.logd("---- response code --- :"+response.code()); return response;
这样子基本可以将请求数据和响应数据都打印出来。所以这就是拦截器,那么我们要达到需要的结果逻辑就是Request先不拦截,拦截每一个请求的响应,然后对响应行中的响应码进行判断,如果是200,则不用管,直接返回该Response给前端,如果是401,则拦截响应体,解析响应体,然后通过后台自己的规则 进行判断,其中我们对status分为两类,一种是402 403 405 406,一种是404,前者所要做的逻辑操作都是一样,Toast message,然后退出登录,回到登录页面,后者因为token过期,需要请求刷新token接口获取新的token,再次发送请求。
第一类以后再说,因为不可能会是在拦截器中做操作的,先看第二类。
首先先拦截到响应码。
Request request = chain.request(); // try the request
Response originalResponse = chain.proceed(request); /**通过如下的办法曲线取到请求完成的数据
*
* 原本想通过 originalResponse.body().string()
* 去取到请求完成的数据,但是一直报错,不知道是okhttp的bug还是操作不当
*
* 然后去看了okhttp的源码,找到了这个曲线方法,取到请求完成的数据后,根据特定的判断条件去判断token过期
*/
ResponseBody responseBody = originalResponse.body(); //首先从response中获取响应码,只判断 401 或 200
int responseCode = originalResponse.code();
获取到响应体的json字符串,也就是responseBody字符串,获取到它以后才能解析
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) {
charset = contentType.charset(UTF8);
} //获取响应体的字符串
String bodyString = buffer.clone().readString(charset); LogUtils.logd("body---------->" + bodyString);
解析
//将字符串解析成bean,然后判断bean里面的字段status
TokenResponse responseB = (TokenResponse) JsonUtils.fromJson(bodyString, TokenResponse.class);
int status = responseB.getStatus();
String message = responseB.getMessege();
对status进行判断
if(status == || status == || status== || status ==){
LogUtils.logd("status:"+status+" message: "+message);
return originalResponse;
}else if(status == ){ //token有效期过了,需重新刷新token
//取出本地的refreshToken
String refreshToken = SPUtils.getSharedStringData(BaseApplication.getAppContext(), AppConstant.REFRESH_TOKEN_KEY); //
HashMap<String, Object> params = new HashMap<>();
params.put("refresh_token", refreshToken);
String jsonString = JsonUtils.toJson(params);
RequestBody body = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),jsonString); // 通过一个特定的接口获取新的token,此处要用到同步的retrofit请求
Call<RefreshTokenBean> call = Api.getApiService().refreshToken(Api.getCacheControl(),body); //要用retrofit的同步方式
RefreshTokenBean bean= call.execute().body(); LogUtils.logd("刷新token获取的新bean "+bean.toString()); //将新的数据用sp更新下保存起来
SPUtils.setSharedStringData(BaseApplication.getAppContext(),AppConstant.ACCESS_TOKEN_KEY,bean.getAccess_token());
SPUtils.setSharedStringData(BaseApplication.getAppContext(),AppConstant.TOKEN_TYPE_KEY,bean.getToken_type());
SPUtils.setSharedStringData(BaseApplication.getAppContext(),AppConstant.USER_ID,bean.getUserinfoId()); String newToken = SPUtils.getSharedStringData(BaseApplication.getAppContext(),AppConstant.ACCESS_TOKEN_KEY); // create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder().header("Authorization",SPUtils.getSharedStringData(BaseApplication.getAppContext(),AppConstant.TOKEN_TYPE_KEY)+" "+newToken)
.build(); originalResponse.body().close();
return chain.proceed(newRequest);
}
上面这部分代码,可能每个人请求不一样,有点点细微差别,但是理解起来就是,如果我们不想管,那就从拦截器中获取到request,以此request返回response即可。
Request request = chain.request();
如果我们觉得该request不对,需要修改,那么就修改,然后用新的request返回新的resonse即可
Request newRequest = request.newBuilder().header("Authorization",SPUtils.getSharedStringData(BaseApplication.getAppContext(),AppConstant.TOKEN_TYPE_KEY)+" "+newToken)
.build();
对于其他的status操作,需要退出登录,回到登录页面的,这里我使用的是retrofit,开始我也不知道怎样获取请求失败的responseBody,然后去J神的Github上搜了下,还真搜到了Retrofit ,在onError方法中竟然可以获取到responseBody,而且J神也提供了方法(可能因为版本不同 方法不一样)
HttpException error = (HttpException)e;
String errorBody = "";
try {
errorBody = error.response().errorBody().string();
} catch (IOException e1) {
e1.printStackTrace();
}
嗯哼,这样子就获取失败的响应,接下来比较简单,大致差不多 清除SP数据 Toast message finish activity , initent login activity。
3.道友留步
这个项目命运多舛,一系列原因导致当初我搭建框架时是很久以前,当初拦截器那里一直想要获取responseBody的String方法,也就是将responseBody转成字符串,找了很多资料,很多方法都不行,然后在一位大神那里学到了下面的方法,挺感谢他的,但是时间太久找不到来源了。
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) {
charset = contentType.charset(UTF8);
}
希望能帮到你吧!!
科学的解决Http Token拦截器TokenInterceptor实现的更多相关文章
- Struts2_使用token拦截器控制重复提交(很少用)
控制重复提交的方式:1.表单提交后页面重定向:2.Struts2.x token拦截器 大致流程: 例子: index.jsp <%@ page language="java" ...
- Ajax请求Session超时的解决办法:拦截器 + 封装jquery的post方法
目标:前端系统,后端系统等,统一处理Session超时和系统错误的问题. 可能需要处理的问题:Session超时.系统500错误.普通的业务错误.权限不足. 同步请求: Sess ...
- 解决Spring Boot 拦截器注入service为空的问题
问题:在自定义拦截器中,使用了@Autowaire注解注入了封装JPA方法的Service,结果发现无法注入,注入的service为空 0.原因分析 拦截器加载的时间点在springcontext之前 ...
- struts2-第二章-拦截器
一,回顾 (1)默认action,404问题;<default-action-ref name="action 名称"/> (2)模块化,package,struts. ...
- 【SpringBoot】拦截器使用@Autowired注入接口为null解决方法
最近使用SpringBoot的自定义拦截器,在拦截器中注入了一个DAO,准备下面作相应操作,拦截器代码: public class TokenInterceptor implements Handle ...
- java:struts框架3(自定义拦截器,token令牌,文件上传和下载(单/多))
1.自定义拦截器: struts.xml: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ...
- springmvc 用拦截器+token防止重复提交
一,原理: 1,在进入到提交页面时,使用拦截器拦截在进入此方法前,生成一个token,放到session中, @RequestMapping(value = "/{id}/details&q ...
- struts2 自带的 token防止表单重复提交拦截器
在struts2中,我们可以利用struts2自带的token拦截器轻松实现防止表单重复提交功能! 1. 在相应的action配置中增加: <interceptor-ref name=&quo ...
- struts2.1.6教程五、拦截器
在前面我们已经初步使用过拦截器,下面继续细细探讨. 1.概述strust2中的拦截器 拦截器是Struts2框架的核心,它主要完成解析请求参数.将请求参数赋值给Action属性.执行数据校验.文件上传 ...
随机推荐
- @EnableAsync和@Async开始异步任务支持
Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程.使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor.在开发中实现异步任务,我们 ...
- IOS计算两点之间的距离
//广州经纬度 CLLocationCoordinate2D guangZhouLocation; guangZhouLocation.latitude = 23.20; guangZhouLocat ...
- thinkphp5项目--企业单车网站(八)(文章板块要点)(删除图片)
thinkphp5项目--企业单车网站(八)(文章板块要点)(删除图片) 项目地址 fry404006308/BicycleEnterpriseWebsite: Bicycle Enterprise ...
- ZOJ 1108 FatMouse's Speed (HDU 1160) DP
传送门: ZOJ:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=108 HDU :http://acm.hdu.edu.cn/s ...
- [Angular] Creating an Observable Store with Rx
The API for the store is really simple: /* set(name: string, state: any); select<T>(name: stri ...
- [Angular2 Router] Resolving route data in Angular 2
From Article: RESOLVING ROUTE DATA IN ANGULAR 2 Github If you know Anuglar UI router, you must know ...
- Android自定义组件系列【10】——随ViewPager滑动的导航条
昨天在用到ViewPager实现滑动导航的时候发现微信的导航条效果是跟随ViewPager的滑动而动的,刚开始想了一下,感觉可以使用动画实现,但是这个滑动是随手指时时变化的,貌似不可行,后来再网上搜了 ...
- ImageButton按压效果失效
LinearLayout中ImageButton的按压效果不起作用,如图 布局如下: <LinearLayout android:id="@id/ll_add_reply_face&q ...
- Python 线程启动的四种方式
import threading,_thread def action(i): print(i **32) #带有状态的子类 class Mythread(threading.Thread): def ...
- JS实现页面table鼠标移动改变tr行颜色,单击tr选中复选框功能
JS源代码: //需要设置tr背景颜色 var highlightcolor='#bfecfc'; //设置背景颜色 function changeto(index){ var tr1 = docum ...