RxJava2 + Retrofit2 完全指南 之 Authenticator处理与Token静默刷新

前言

今年是9102年了,应该没有还在用userId来鉴权了吧,也应该很少人使用cookie来保持会话了吧?而现在更常用的是Authorization,

关于Authorization

简略的讲一讲Authorization,如果要深入了解的话请看底部的参考文章链接。Authorization的认证方式在我接触中有两种

  • Basic
  • Bearer

Basic

HTTP基本认证,在请求的时候加上以下请求头:

Authorization : basic base64encode(username+":"+password))

将用户名和密码用英文冒号(:)拼接起来,并进行一次Base64编码。服务端拿到basic码,然后自己查询相关信息再按照base64encode(username+":"+password))的方式得出当前用户的basic进行对比。

Bearer

授权完成后会返回类似下面的数据结构:

{
    "token_type": "Bearer",
    "access_token": "xxxxx",
    "refresh_token": "xxxxx"
}

而其中的refresh_token的作用是在access_token失效的时候进行重新刷新传入的参数,具体怎么传要看各自项目的实现方式。
access_token就是我们的认证令牌。token_type是令牌的类型,而我现在使用到的只有bearer,其它类型未碰到,希望各位看官能补充一下。
在使用的时候需要加上以下请求头:

Authorization : token_type access_token

也就是这样:

Authorization: Bearer xxxxx

实现

方式1 :authenticator

authenticator是在创建OkHttpClient的时候能够设置的一个方法,接收的是一个okhttp3.Authenticator的interface,默认不设置的话是一个NONE的空实现,而回调的地方是在okhttp3.internal.http.RetryAndFollowUpInterceptor.followUpRequest()

Authenticator
followUpRequest

编码

相关代码也比较简单,在okhttp3.Authenticator的注释上面也写有简单的例子,核心代码就以下几行:

private Authenticator authorization = new Authenticator() {
        @Override
        public Request authenticate(Route route, Response response) throws IOException {

            //-----------核心代码-------
            // 这里抛出的错误会直接回调 onError
            // 这里发起的请求是同步的,刷新完成token后再增加到header中
            // String token = refreshToken();
            String token = Credentials.basic("userName", "password", Charset.forName("UTF-8"));
            return response.request()
                    .newBuilder()
                    .header("Authorization", token)
                    .build();
            //-----------核心代码-------
        }
    };

以上就是主要代码,其中演示的是basic方式的认证模式,bearer方式的没实现,其实也只是refreshToken()中发起一个同步请求去刷新一下token并保存,后面的步骤都是一样的。

如何使用

创建OkHttpClient调用,当然,也可以直接写匿名内部类的实现,都是可以的。

retrofit = new Retrofit.Builder()
                .client(new OkHttpClient.Builder()
                        .authenticator(authorization)// 增加重试
                        .addInterceptor(getHttpLoggingInterceptor())
                        .build())
                .baseUrl("https://api.github.com/")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

演示

为了演示方便看到结果,我在Authenticator的实现类中增加了一些回调主线程的方法,具体看一下源码即可,对于主要结果没什么影响。

Authenticator

总结

使用官方提供的Authenticator有一个很明显的问题,那就是会占用重试,像示例中,我并没有传入一个正确的token,就导致一直在回调Authenticator,直到达到了最大重试次数为止。而往往需求是token失效以后选择重试一次,成功了继续请求,再次失败则提示登录,所以这个方法使用得不多。

方式2 :Interceptor

上面okhttp3.Authenticator的实现方式其实是在RetryAndFollowUpInterceptor中判断和回调的,由此,可以自定义一个Interceptor,由开发者来自行判断和跳转。

编码

详细代码如下:

Interceptor mAuthenticatorInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        // 获取请求
        Request request = chain.request();
        // 获取响应
        Response response = chain.proceed(request);
        // 在这里判断是不是是token失效
        // 当然,判断条件不会这么简单,会有更多的判断条件的
        if (response.code() == 401) {
            // 这里应该调用自己的刷新token的接口
            // 这里发起的请求是同步的,刷新完成token后再增加到header中
            // 这里抛出的错误会直接回调 onError
//                String token = refreshToken();
            String token = Credentials.basic("userName", "password", Charset.forName("UTF-8"));
            // 创建新的请求,并增加header
            Request retryRequest = chain.request()
                    .newBuilder()
                    .header("Authorization", token)
                    .build();

            // 再次发起请求
            return chain.proceed(retryRequest);
        }

        return response;
    }
}

使用

和方法一相同,在创建HttpClient的时候addInterceptor(mAuthenticatorInterceptor),将我们自己的拦截器加入进行即可。

演示

AuthenticatorInterceptor

总结

从演示中可以看出,在第一次返回401的时候,进行了一次token的获取,并且再次进行了请求,圆满符合我们的预期,只重试一次。

最后

分析

可能会有疑问:为什么使用Interceptor就能达到我们预期的效果?Interceptor到底是如何工作的?

首先Interceptor添加是有先后顺序的,首先添加的是我们设置的Interceptor,然后添加的才是okhttpInterceptor。如源码中:

Add Interceptor

总的来说,okhttp的实现方式就是通过Interceptor来组成一个一个的chian来实现的。每个Interceptor里面的intercept()方法内部都会调用Chain.proceed()方法,将请求交给下一个Interceptor,由此类推,一直到最后一个Interceptor请求完成。
需要注意的是proceed是同步的,也就是调用proceed方法之后需要等等下一个Interceptor进行处理,当最后一个Interceptor请求到数据,经过自己的处理之后,再往上返回Response,直到第一个Interceptor为止,返回数据。主要关系如下图:

Interceptor ex

这些所有的Interceptor里面的proceed都是调用了一次,那么我们增加一个Interceptor,等到proceed返回了Response之后,对Response进行判断,如果是认证失败,我们则刷新一下token,重新创建Request,再调用一次proceed方法。如果再失败了,就不会再回调到当前的Interceptor,如下图:

AuthenticatorInterceptor

源码

参考文章

微信公众号

AndroidRookie
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容