Token的静默刷新(AccessToken过期后使用RefreshToken进行刷新)

本文参考:
https://www.jianshu.com/p/7a2d2d7497a1(主要参考)
https://stackoverflow.com/questions/22450036/refreshing-oauth-token-using-retrofit-without-modifying-all-calls

1.问题出现场景

我们知道,现在身份验证中一般会有两个Token:
AccessTokenRefreshToken
在每次请求中,我们需要把AccessToken加入到header中来进行请求,否则会因为身份认证不通过,导致401错误,请求失败。
但是AccessToken是会过期的,通常过期时间在12小时或者一天等待(不同企业不同定制),但是RefreshToken过期时间是一个月甚至更久。
所以在AccessToken过期后,我们需要使用RefreshToken来进行刷新AccessToken

2.两个实现思路

方式一.主动刷新:
AccessToken是JWT数据,可以解析过期时间,甚至有些登陆接口会直接返回过期时间,所以在每次将AccessToken加入到Header之前,判断AccessToken是否过期,如果过期,主动进行刷新
在线解析JWT数据:http://jwt.calebb.net/

方式二.全局拦截401错误,被动刷新(推荐):
在每次请求中设置拦截器,拦截到401错误时,进行AccessToken刷新,刷新成功后,将新的AccessToken加入到Header,再次发出请求
这种方式对于用户来说完全无感,且不会出现疏漏 推荐

3.使用Interceptor来实现静默刷新(方式二)

注:官方推荐使用Authorization来进行,这里不使用的原因请看参考文章一
首先我们完成拦截器代码
新建ClassAuthenticatorInterceptor 实现接口Interceptor
以下是 AuthenticatorInterceptor.class 拦截器代码

class AuthenticatorInterceptor : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        // 拦截获取请求
        val request = chain.request()
        // 获取响应
        val response = chain.proceed(request)
        // 拦截401错误进行处理
        if (response.code() == 401) {
            // 这里应该调用自己的刷新token的接口
            // 这里发起的请求是同步的,刷新完成token后再增加到header中
            // 这里抛出的错误会直接回调 之前接口的onError方法
            //updateToken()就是我进行刷新accessToken的方法,会返回新的accessToken
            val newApiToken = updateToken()
            // 创建新的请求,并增加header
            val retryRequest = chain.request()
                    .newBuilder()
                    .header("Authorization", newApiToken)
                    .build()
            // 再次发起请求
            return chain.proceed(retryRequest)
        }
        //如果没有发生401错误则不进行操作,直接返回原本的请求
        return response
    }

//刷新accessToken方法
//注解  @Synchronized 来保证同一时间只能由一个线程来调用此方法
    @Synchronized fun updateToken(): String {
        var newApiToken = ""
        var refushToken = ""//从数据中拿到refushToken

//调用updateToken的服务接口,传入refushToken来刷新
//execute()为同步请求 这里必须使用同步请求
//这里只使用到了retrofit没有使用rxjava
//固定值
//refresh_token="refresh_token"
//grant_type="openid"
        var data = service.updateToken(refushToken, "refresh_token", "openid")
                .execute()
//根据请求得到的数据data的code判断同步请求是否成功
//200就是成功了
        if (data.code()==200){
//获取到请求返回数据体
            var t=data.body()
//t是请求接口的实体类对象
            if (t!=null){
//这里获取到新的accessToken
                val actualToken = t.tokenType + " " + t.accessToken
                newApiToken = actualToken
//同时Token可以进行保存操作
//...
            }
//返回新的accessToken
            return newApiToken

        }
        return newApiToken
    }
}

以上就是拦截器的代码了,总结一下我们在这个拦截器中,拦截到了401错误,然后刷新Token,使用新的accessToken再次发出请求
那么我们怎么将拦截器使用起来呢?请看:
在项目中统一创建retrofit对象的地方加入拦截器
加入拦截器代码:



    fun initClint(): OkHttpClient {
        val builder = OkHttpClient.Builder()
        builder.addInterceptor(AuthenticatorInterceptor())
//此处还可以builder.addInterceptor()添加其他拦截器 比如log打印日志什么的
        return builder.build()
    }

        Retrofit.Builder()
                .baseUrl(Constants.BASE_URL)
                .client(initClint())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build()

现在就实现了请求401错误拦截并处理

4.补充一点小说明

这是刷新accessToken的api接口

    @FormUrlEncoded
    @Headers(
            "Accept:application/json",
            "Content-Type:application/x-www-form-urlencoded",
            "charset:UTF-8"
    )
    @POST("你的接口")
    fun updateToken(
            @Header("Authorization") apiToken:String,
            @Field("refresh_token") freshToken: String,
            @Field("grant_type") refreshToken: String,
            @Field("scope") scope: String
    ): Call

这是service单独创建retrofit对象的方法:

    fun updateToken(refresh_token:String,grantType: String, scope: String): Call {

        return Retrofit
                .Builder()
                .baseUrl(Constants.BASE_URL)
                .client(initClint())
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(CommonApi::class.java).updateToken("固定授权码",refresh_token,grantType,scope)
    }

这里我们使用了Call来发出请求,而不是Observable,是为了调同call..execute()发出同步请求,所以对于刷新Token的接口不需要使用到rxjava

你可能感兴趣的:(Token的静默刷新(AccessToken过期后使用RefreshToken进行刷新))