前言
今年是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()
編碼
相關代碼也比較簡單,在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
有一個很明顯的問題,那就是會占用重試
,像示例中,我并沒有傳入一個正確的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)
,將我們自己的攔截器加入進行即可。
演示
總結
從演示中可以看出,在第一次返回401
的時候,進行了一次token的獲取,并且再次進行了請求,圓滿符合我們的預期,只重試一次。
最后
分析
可能會有疑問:為什么使用
Interceptor
就能達到我們預期的效果?Interceptor
到底是如何工作的?
首先Interceptor
添加是有先后順序的,首先添加的是我們設置的Interceptor
,然后添加的才是okhttp
的Interceptor
。如源碼中:
總的來說,okhttp
的實現方式就是通過Interceptor
來組成一個一個的chian
來實現的。每個Interceptor
里面的intercept()
方法內部都會調用Chain.proceed()
方法,將請求交給下一個Interceptor
,由此類推,一直到最后一個Interceptor
請求完成。
需要注意的是proceed
是同步的,也就是調用proceed
方法之后需要等等下一個Interceptor
進行處理,當最后一個Interceptor
請求到數據,經過自己的處理之后,再往上返回Response
,直到第一個Interceptor
為止,返回數據。主要關系如下圖:
這些所有的Interceptor
里面的proceed
都是調用了一次,那么我們增加一個Interceptor
,等到proceed
返回了Response
之后,對Response
進行判斷,如果是認證失敗,我們則刷新一下token,重新創建Request,再調用一次proceed
方法。如果再失敗了,就不會再回調到當前的Interceptor
,如下圖: