前言
今年是9102年了,應(yīng)該沒(méi)有還在用userId來(lái)鑒權(quán)了吧,也應(yīng)該很少人使用cookie來(lái)保持會(huì)話了吧?而現(xiàn)在更常用的是Authorization,
關(guān)于Authorization
簡(jiǎn)略的講一講Authorization,如果要深入了解的話請(qǐng)看底部的參考文章鏈接。Authorization的認(rèn)證方式在我接觸中有兩種
- Basic
- Bearer
Basic
HTTP基本認(rèn)證,在請(qǐng)求的時(shí)候加上以下請(qǐng)求頭:
Authorization : basic base64encode(username+":"+password))
將用戶名和密碼用英文冒號(hào)(:)拼接起來(lái),并進(jìn)行一次Base64編碼。服務(wù)端拿到basic碼,然后自己查詢相關(guān)信息再按照base64encode(username+":"+password))的方式得出當(dāng)前用戶的basic進(jìn)行對(duì)比。
Bearer
授權(quán)完成后會(huì)返回類(lèi)似下面的數(shù)據(jù)結(jié)構(gòu):
{
"token_type": "Bearer",
"access_token": "xxxxx",
"refresh_token": "xxxxx"
}
而其中的refresh_token的作用是在access_token失效的時(shí)候進(jìn)行重新刷新傳入的參數(shù),具體怎么傳要看各自項(xiàng)目的實(shí)現(xiàn)方式。
access_token就是我們的認(rèn)證令牌。token_type是令牌的類(lèi)型,而我現(xiàn)在使用到的只有bearer,其它類(lèi)型未碰到,希望各位看官能補(bǔ)充一下。
在使用的時(shí)候需要加上以下請(qǐng)求頭:
Authorization : token_type access_token
也就是這樣:
Authorization: Bearer xxxxx
實(shí)現(xiàn)
方式1 :authenticator
authenticator是在創(chuàng)建OkHttpClient的時(shí)候能夠設(shè)置的一個(gè)方法,接收的是一個(gè)okhttp3.Authenticator的interface,默認(rèn)不設(shè)置的話是一個(gè)NONE的空實(shí)現(xiàn),而回調(diào)的地方是在okhttp3.internal.http.RetryAndFollowUpInterceptor.followUpRequest()


編碼
相關(guān)代碼也比較簡(jiǎn)單,在okhttp3.Authenticator的注釋上面也寫(xiě)有簡(jiǎn)單的例子,核心代碼就以下幾行:
private Authenticator authorization = new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
//-----------核心代碼-------
// 這里拋出的錯(cuò)誤會(huì)直接回調(diào) onError
// 這里發(fā)起的請(qǐng)求是同步的,刷新完成token后再增加到header中
// String token = refreshToken();
String token = Credentials.basic("userName", "password", Charset.forName("UTF-8"));
return response.request()
.newBuilder()
.header("Authorization", token)
.build();
//-----------核心代碼-------
}
};
以上就是主要代碼,其中演示的是basic方式的認(rèn)證模式,bearer方式的沒(méi)實(shí)現(xiàn),其實(shí)也只是refreshToken()中發(fā)起一個(gè)同步請(qǐng)求去刷新一下token并保存,后面的步驟都是一樣的。
如何使用
創(chuàng)建OkHttpClient調(diào)用,當(dāng)然,也可以直接寫(xiě)匿名內(nèi)部類(lèi)的實(shí)現(xiàn),都是可以的。
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();
演示
為了演示方便看到結(jié)果,我在Authenticator的實(shí)現(xiàn)類(lèi)中增加了一些回調(diào)主線程的方法,具體看一下源碼即可,對(duì)于主要結(jié)果沒(méi)什么影響。

總結(jié)
使用官方提供的Authenticator有一個(gè)很明顯的問(wèn)題,那就是會(huì)占用重試,像示例中,我并沒(méi)有傳入一個(gè)正確的token,就導(dǎo)致一直在回調(diào)Authenticator,直到達(dá)到了最大重試次數(shù)為止。而往往需求是token失效以后選擇重試一次,成功了繼續(xù)請(qǐng)求,再次失敗則提示登錄,所以這個(gè)方法使用得不多。
方式2 :Interceptor
上面okhttp3.Authenticator的實(shí)現(xiàn)方式其實(shí)是在RetryAndFollowUpInterceptor中判斷和回調(diào)的,由此,可以自定義一個(gè)Interceptor,由開(kāi)發(fā)者來(lái)自行判斷和跳轉(zhuǎn)。
編碼
詳細(xì)代碼如下:
Interceptor mAuthenticatorInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
// 獲取請(qǐng)求
Request request = chain.request();
// 獲取響應(yīng)
Response response = chain.proceed(request);
// 在這里判斷是不是是token失效
// 當(dāng)然,判斷條件不會(huì)這么簡(jiǎn)單,會(huì)有更多的判斷條件的
if (response.code() == 401) {
// 這里應(yīng)該調(diào)用自己的刷新token的接口
// 這里發(fā)起的請(qǐng)求是同步的,刷新完成token后再增加到header中
// 這里拋出的錯(cuò)誤會(huì)直接回調(diào) onError
// String token = refreshToken();
String token = Credentials.basic("userName", "password", Charset.forName("UTF-8"));
// 創(chuàng)建新的請(qǐng)求,并增加header
Request retryRequest = chain.request()
.newBuilder()
.header("Authorization", token)
.build();
// 再次發(fā)起請(qǐng)求
return chain.proceed(retryRequest);
}
return response;
}
}
使用
和方法一相同,在創(chuàng)建HttpClient的時(shí)候addInterceptor(mAuthenticatorInterceptor),將我們自己的攔截器加入進(jìn)行即可。
演示

總結(jié)
從演示中可以看出,在第一次返回401的時(shí)候,進(jìn)行了一次token的獲取,并且再次進(jìn)行了請(qǐng)求,圓滿符合我們的預(yù)期,只重試一次。
最后
分析
可能會(huì)有疑問(wèn):為什么使用
Interceptor就能達(dá)到我們預(yù)期的效果?Interceptor到底是如何工作的?
首先Interceptor添加是有先后順序的,首先添加的是我們?cè)O(shè)置的Interceptor,然后添加的才是okhttp的Interceptor。如源碼中:

總的來(lái)說(shuō),okhttp的實(shí)現(xiàn)方式就是通過(guò)Interceptor來(lái)組成一個(gè)一個(gè)的chian來(lái)實(shí)現(xiàn)的。每個(gè)Interceptor里面的intercept()方法內(nèi)部都會(huì)調(diào)用Chain.proceed()方法,將請(qǐng)求交給下一個(gè)Interceptor,由此類(lèi)推,一直到最后一個(gè)Interceptor請(qǐng)求完成。
需要注意的是proceed是同步的,也就是調(diào)用proceed方法之后需要等等下一個(gè)Interceptor進(jìn)行處理,當(dāng)最后一個(gè)Interceptor請(qǐng)求到數(shù)據(jù),經(jīng)過(guò)自己的處理之后,再往上返回Response,直到第一個(gè)Interceptor為止,返回?cái)?shù)據(jù)。主要關(guān)系如下圖:

這些所有的Interceptor里面的proceed都是調(diào)用了一次,那么我們?cè)黾右粋€(gè)Interceptor,等到proceed返回了Response之后,對(duì)Response進(jìn)行判斷,如果是認(rèn)證失敗,我們則刷新一下token,重新創(chuàng)建Request,再調(diào)用一次proceed方法。如果再失敗了,就不會(huì)再回調(diào)到當(dāng)前的Interceptor,如下圖:

源碼
參考文章
- OAuth2.0協(xié)議原理與實(shí)現(xiàn):協(xié)議原理
- OAuth2.0協(xié)議原理與實(shí)現(xiàn):TOKEN生成算法
- OAuth2.0協(xié)議原理與實(shí)現(xiàn):協(xié)議實(shí)現(xiàn)
微信公眾號(hào)
